Updated 2026/09/26
You cannot use simple command line clients to send email to Gmail or Yahoo domains because of the extra authentication that is required [1][2].
One of the features of Proton Mail is that you can send emails through their SMTP servers without logging in through a web browser. This feature is only available for business accounts.
Proton mail refers to this as SMTP Submission
Their most economical plan for doing this is "Mail Professional" ($10/month).
The "Simple" Mail Transfer Protocol (SMTP) is not that simple. To reduce the complexity go between programs were developed such as ssmtp, msmtp and swaks.
Unfortunately, with these programs, comes limitations. For example, ssmpt only support two authentication methods: LOGIN and CRAM-MD5.
Proton's smtp-submission requires that the authentication method be PLAIN.
Furthermore, ssmtp has not been maintained since 2019. Debian suggests using msmtp as its replacement. However, I found it to be more complex than swaks.
However, because of spammer, the authentication for logging into one of these servers via SMPT has become complex.
Originally, I used Swaks (Swiss Army Knife for smpt). Later, I learned that I could do the same with cURL.
Extended SMPT (ESMTP) expands the original protocol to include email attachments, TLS, and other capabilities. Today, almost all email clients and email services use ESMTP, not basic SMTP.
SMTP and ESMPT use port 587.
According to Reference [4] Proton Mail use port 1025, while most SMTP servers use port 587. My experence has been that port 587 works fine with Proton Mail.
Looking at reference [3], you may be able to generate your own script file to send an email to proton mail.
The basic Internet Message Format used for email is defined by RFC 5322. Internet email messages consist of two sections, "header and "body". The header is separated from the body by a blank line.
Typically, the header includes To:, From:, Subject:, Date: etc.As long as the email headers and body that you wanted to send is in a file, cURL is easy to use. For example:
#!/bin/bash
curl --ssl smtp://smtp.protonmail.ch:587 \
--mail-from ray@franco.ms \
--mail-rcpt ray@rayfranco.com \
--user 'ray@franco.ms:My_Redactecd_SMTP_Password' \
--upload-file MESSAGE.txt
where MESSAGE.txt is:
From: ray@franco.ms
To: ray@rayfranco.com
Subject: Server Status
The server is up.
Notes:
The problem is if you do not use the --upload-file <file_name> option (or its short form -T <file_name>), then cURL send a VFRY (verify) command to the mail service to verify that the mailbox is valid (exist). However, for security reasons Proton Mail disables VFRY.
Thus, you cannot use the --data option, and you must use the --upload-file <file_name> option. However, when you use the --upload-file <file_name> with "Process Substitution" instead of a actual file, curl does not know what the use for HELO or EHLO, so the SMTP server gives the error: "Helo command rejected: Invalid name"
The solution, for the Helo error, is to include the client's Helo (or extended helo -ehlo) with the url. That is use: url smtp.protonmail.ch:587/127.0.0.1 where 127.0.0.1 is "used" as the client's EHLO. According to Reference [8] SMTP does not check your domain name or IP address, so it will accept almost anything for EHLO.
The code below uses cURL in a bash function to send a status email:
#!/bin/bash
# -------- function send_email ---------
send_email () {
MESSAGE="From: ray@franco.ms\nTo: ray@rayfranco.com\nSubject: Server Status\n\nThe Server is $1 - $(date)\n"
curl --verbose \
--url smtp.protonmail.ch:587/127.0.0.1 \
--ssl-reqd \
--user 'ray@franco.ms:My_Redacted_SMTP_Password' \
--mail-from ray@franco.ms \
--mail-rcpt ray@rayfranco.com \
--upload-file <(echo -e "$MESSAGE")
}
# ---------- main script --------
send_email UP
Note the last line of the send_email function is:
--upload-file <(echo -e $MESSAGE)
which is "Process Substitution", <(...) - not redirection and command line substitution.
The verbose output is:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Host smtp.protonmail.ch:587 was resolved.
* IPv6: (none)
* IPv4: 176.119.200.135, 185.70.42.135, 185.205.70.135
* Trying 176.119.200.135:587...
* Connected to smtp.protonmail.ch (176.119.200.135) port 587
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0< 220 mailsubzur1002.protonmail.ch ESMTP Postfix
> EHLO 127.0.0.1
< 250-mailsubzur1002.protonmail.ch
< 250-PIPELINING
< 250-SIZE 36480000
< 250-STARTTLS
< 250-ENHANCEDSTATUSCODES
< 250-8BITMIME
< 250 CHUNKING
> STARTTLS
< 220 2.0.0 Ready to start TLS
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [1555 bytes data]
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [6 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [4405 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [520 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS
* Server certificate:
* subject: CN=protonmail.com
* start date: Sep 7 13:14:28 2026 GMT
* expire date: Dec 6 13:14:27 2026 GMT
* subjectAltName: host "smtp.protonmail.ch" matched cert's "*.protonmail.ch"
* issuer: C=US; O=Let's Encrypt; CN=YR1
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 3: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to smtp.protonmail.ch (176.119.200.135) port 587
} [5 bytes data]
> EHLO 127.0.0.1
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [233 bytes data]
< 250-mailsubzur1002.protonmail.ch
< 250-PIPELINING
< 250-SIZE 36480000
< 250-AUTH PLAIN LOGIN
< 250-ENHANCEDSTATUSCODES
< 250-8BITMIME
< 250 CHUNKING
} [5 bytes data]
> AUTH PLAIN
{ [5 bytes data]
< 334
} [5 bytes data]
> AHJheUBmcmFuY28ubXMAWE00MlhGUjZHMlFDNDlRTA==
0 0 0 0 0 0 0 0 --:--:-- 0:00:02 --:--:-- 0{ [5 bytes data]
< 235 2.7.0 Authentication successful
} [5 bytes data]
> MAIL FROM:
{ [5 bytes data]
< 250 2.1.0 Ok
} [5 bytes data]
> RCPT TO:
{ [5 bytes data]
< 250 2.1.5 Ok
} [5 bytes data]
> DATA
0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0{ [5 bytes data]
< 354 End data with .
} [5 bytes data]
* upload completely sent off: 123 bytes
100 123 0 0 0 123 0 26 --:--:-- 0:00:04 --:--:-- 26{ [5 bytes data]
< 250 2.0.0 Ok: queued as 4hrQTq4HDGz1DF4m
100 123 0 0 0 123 0 24 --:--:-- 0:00:05 --:--:-- 25
* Connection #0 to host smtp.protonmail.ch left intact
curl SMTP has a catch 22. You have to use the --upload-file option, which means you have to write and read to disk, or you have to use "Process Substitution". Unfortunately, "Process Substitution" is not defined for Portable Operating System Interface (POSIX) shells (e.g. -dash). Also, POSIX shells do not have the -e option for the echo command. You might be able to get around this by storing the message in a temporary ram disk file such as "/dev/shm" (shared memory).
swaks stands for: Swiss army knife for smtp
Swaks was first released in 2003 [1]. As of September 2026, the latest version is 20240103.0, which was released in 2024. Swaks was written and maintained by an individual: John Jetmore. Swaks is in the Debian and Raspberry Pi repositories. It is not installed by default. To install it:
sudo apt install swaks
In the following examples:
#!/bin/bash
#----- function send_emai -------
send_email () {
MESSAGE="From: ray@franco.ms\nTo: ray@rayfranco.com\nSubject: Server Status via SWAKS\n\nThe Server is $1 - $(date)\n"
swaks \
--from ray@franco.ms \
--to ray@rayfranco.com \
--server smtp.protonmail.ch \
--port 587 \
--auth PLAIN \
--tls \
--auth-user 'ray@franco.ms' \
--auth-password 'My_Redacted_SMTP_Password' \
--ehlo 127.0.0.1 \
--data "$MESSAGE"
}
#-------- Main ----------
send_email UP
SWAKS, outputs SMTP communications by default. There is no need for the verbose option. The output is:
=== Trying smtp.protonmail.ch:587... === Connected to smtp.protonmail.ch. <- 220 mailsubosl1001.protonmail.ch ESMTP Postfix -> EHLO 127.0.0.1 <- 250-mailsubosl1001.protonmail.ch <- 250-PIPELINING <- 250-SIZE 36480000 <- 250-STARTTLS <- 250-ENHANCEDSTATUSCODES <- 250-8BITMIME <- 250 CHUNKING -> STARTTLS <- 220 2.0.0 Ready to start TLS === TLS started with cipher TLSv1.3:TLS_AES_256_GCM_SHA384:256 === TLS client certificate not requested and not sent === TLS no client certificate set === TLS peer[0] subject=[/CN=protonmail.com] === commonName=[protonmail.com], subjectAltName=[DNS:*.pm.me, DNS:*.protonmail.ch, DNS:*.protonmail.com, DNS:*.protonvpn.ch, DNS:*.protonvpn.com, DNS:protonmail.com] notAfter=[2026-12-06T13:14:27Z] === TLS peer[1] subject=[/C=US/O=Let's Encrypt/CN=YR1] === commonName=[YR1], subjectAltName=[] notAfter=[2028-09-02T23:59:59Z] === TLS peer[2] subject=[/C=US/O=ISRG/CN=Root YR] === commonName=[Root YR], subjectAltName=[] notAfter=[2032-09-02T23:59:59Z] === TLS peer certificate passed CA verification, passed host verification (using host smtp.protonmail.ch to verify) ~> EHLO 127.0.0.1 <~ 250-mailsubosl1001.protonmail.ch <~ 250-PIPELINING <~ 250-SIZE 36480000 <~ 250-AUTH PLAIN LOGIN <~ 250-ENHANCEDSTATUSCODES <~ 250-8BITMIME <~ 250 CHUNKING ~> AUTH PLAIN AHJheUBmcmFuY28ubXMAWE00MlhGUjZHMlFDNDlRTA== <~ 235 2.7.0 Authentication successful ~> MAIL FROM:<~ 250 2.1.0 Ok ~> RCPT TO: <~ 250 2.1.5 Ok ~> DATA <~ 354 End data with . ~> From: ray@franco.ms ~> To: ray@rayfranco.com ~> Subject: Server Status via SWAKS ~> ~> The Server is UP - Thu Sep 24 08:31:03 AM CDT 2026 ~> ~> . <~ 250 2.0.0 Ok: queued as 4hrF8p4RNCz1DDLL ~> QUIT <~ 221 2.0.0 Bye === Connection closed with remote host.
The "--from ..." and "--to ..." options are what the SMTP server uses to send and recieve emails.
The "From: ..." and "To: ..." in the data or message are what the recipient's client uses when it displays the email's sender and recipient. If you do not include the "From: ..." and "To: ..." in the data or message, then the recipient's email client will use value in the "--from ..." option as the sender, but for the recipient, it will say "undisclosed".
In lieu of putting the headers and email body in the one message, SWAKS allows both header and body options. The code below does the same as the previous SWAKS code:
#!/bin/bash
#----- function send_emai -------
send_email () {
SUBJECT="Sever Status via SWAKS 2"
BODY="The Server is $1 - $(date)\n"
swaks \
--from ray@franco.ms \
--to ray@rayfranco.com \
--server smtp.protonmail.ch \
--port 587 \
--auth PLAIN \
--tls \
--auth-user 'ray@franco.ms' \
--auth-password 'My_Redacted_SMTP_Password' \
--ehlo 127.0.0.1 \
--header "Subject: $SUBJECT" \
--body "$BODY"
}
#-------- Main ----------
send_email UP
There is no "From: ..." or "To: ..." in the header or body. In the case, the recipient's client will use the values in the options for BOTH the sender and recipient.
Where I ran into problems with SWAKS was trying to put today's date in the subject heading. I tried, $(date), $(date "+%D"), $(date "+%Y/%m/%d), $(date "+%Y-%m-%d"), $(date "+%Y %m %d"). It gave me a error, and it would not send the email. Finally, I tried $(date "+%Y_%m_%d"), and this worked. In the documentation, there is comment about not using a dash in the SWAKs configuration file, but that is all I found. This is not well documented.
Posix Shells do not have:
Watchdog timers require counter. You can use a /dev/shm/file to store the count. For example:
echo 0 > /dev/shm/counter
Will create the file, counter, whose contents is zero.
You can then read the file counter into a varable, increment it, and write it back to the file, counter.
var=$(</dev/shm/counter)
echo $[$var+1] > /dev/shm/counter
Proton Calendar allows you to share your calendar-data with anyone by creating a public link to your calendar's data. Clicking on the link downloads a calendar.ics file (ics - internet calendaring and scheduling).
You can create a link to a full view calendar.ics or a limited view calendar. A limited view calendar shows only when you are busy, without event details.
A calendar.ics is just a text file that can be read by a text editor or the Linux cat command. To view the data in a calendar you must import the ics file into a calendar rendering program (e.g. Google Calendar, Outlook Calendar, GNONE Calendar, etc.).
This is fine, but what I wanted was a way to share a link to a calendar that would displayed my data. Fortunately, I found, on the Internet, a java script program that would do just that.
Proton Calendar does not have a way to mark events as busy or free. My work around for this is to have two calendars one for busy and another for free. The busy events are red, and the free events are green. When you create an event, you can select which calendar to place the event in. In addition, I also have a third calendars to keep up with my wife, which is brown.
Almost all calendar rendering programs allow you to import more than one ics file. The java script program I found included this feature.
In HTML, you paste a url (link) into an anker-href statement and let the user click on the link to download a file.
In bash, it is more complicated. You use a web browser and the calendar-data link provided by Proton to generate a curl (client for url) command.
The steps depend on which web browser you are using.For the FireFox browser:
This will result in a Linux curl command being copied to the the clipboard. Paste this command into your script file.
For example, the link to my Limited View Busy Calendar-data provide by Proton was:
https://calendar.proton.me/api/calendar/v1/url/0lTcLOr_GkvsbbQiqbjwFJIGzfQzvEuC3JyDX1QzC6DJM80Gaqf0sxu9vX0qcEgA6GXNqTNAvxXy7nSe5I6ZXg==/calendar.ics?CacheKey=1FsehvWfOQ5hDzQ8-AedPg%3D%3D
and the curl command generated by the Firefox browser was:
curl 'https://calendar.proton.me/api/calendar/v1/url/0lTcLOr_GkvsbbQiqbjwFJIGzfQzvEuC3JyDX1QzC6DJM80Gaqf0sxu9vX0qcEgA6GXNqTNAvxXy7nSe5I6ZXg==/calendar.ics?CacheKey=1FsehvWfOQ5hDzQ8-AedPg%3D%3D' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
-H 'DNT: 1' \
-H 'Sec-GPC: 1' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Connection: keep-alive' \
-H 'Cookie: AUTH-fwjgufisza6sbyghnpes7lmebqbjmxgh=3fatoxqqotrddgcampn77lcgo5g4amii; Session-Id=aWejKrYjDk21VuTv9uMiLAAAAIY; iaas=WzJd; Tag=default; Domain=proton.me; st=eyJ0IjoicCIsInAiOiJtYWlsYml6MjAyNCIsImMiOjEyfQ' \
-H 'Sec-Fetch-Dest: document' \
-H 'Sec-Fetch-Mode: navigate' \
-H 'Sec-Fetch-Site: none' \
-H 'Sec-Fetch-User: ?1' \
-H 'Priority: u=0, i' \
-H 'TE: trailers'
If you past the above curl command, as is, into a bash script file, the output will by default go to screen (std out). To send the output of the curl command to a file use the -o option. That is: curl -o limited-busy-calender.ics 'https://calendar.proton.me/api/calen...'.
In my case I have four calendars:
and my bash script is four curl statements. One for each calendar.
The three full_view calendars are imported into one instance of a java script calendar rendering program for my viewing, and the limited_view_busy.ics file is imported into another instance of the same java script calendar rendering program for sharing with others. The later is used for others to check my availablity when scheduling multi-party joint examinations.
On the Internet, I found a application, JS_Calendar, based on Java Script that would render ICS Calendar-Data [2]. Unforntately, I know almost nothing about java script, but eventually I got to work.
The java script program, JS_Calendar, is not a single file and it has four directories. To kept my ics files seperate, I added an ics directory.
Forntuately, all the modications you must to make to render you ics data is in one file, "js/custem_display.js". The ics files to import into the calendar are at the very top of js/custom_display.js. They are:
ics_sources = [
{url:'https://sogo.nomagic.uk/SOGo/dav/public/contact/Calendar/3D08-5CC47000-1-5EA59B00.ics', title:'Nomagic Calendar', event_properties:{color: 'SeaGreen'}},
{url:'https://nomagic.uk/calendars/gov.uk/events.ics', title: 'UK Bank Holidays in England and Wales', event_properties: {color: 'DodgerBlue'}},
{url:'https://nomagic.uk/calendars/gouv.fr/events.ics', title: 'French Bank Holidays in Metropole', event_properties: {color: 'DeepPink'}}
]
change to:
// Edit your ics sources here
ics_sources = [
{url:'ics/limited_view_busy.ics', title: 'Availability', event_properties: {color: 'DeepPink'}}
]
Note, that I created the ics directory
To change the calendar view from British, with the first day of the week being Moday, to a US calencar, with the first day of the week being sunday find (around line 60):
firstDay: '1',
local: 'uk',
and change to:
firstDay: '0',
local: 'us',
These changes are optional, but recommended
To view the calendar with a small screen such as a cell phone, add the following line under the header title:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Find the h1 header:
MyEntitiy - Our selected events feeds
This appears above the calendar, change to to something more appropriate, such as:
Ray's Calendar
The first CORS issue was using a file for a URL. This causes a CORS error [4]. You must have an http server. You can set up a local http server using python [5]. However, this does not get you around the second issue. You must have permission from the http(s) file server to access the files. This can only be done if you own the http(s) file server. That is, you must modify the http file server to allow you to access to its files. This is done on Apache by placing a ".htaccess" file in the directory were the resources you want access to are located [6]. Note,to get this to work, I had to use single quotation marks and not the double quotation marks in [7].
I also observed that my calendar did not populate events on the iPhone or the Safari browser (MacOS). Part of this problem was with the server where my website is hosted. On MacOS, Safari showed it as insecure, while Firefox showed it as secure. This was causing CORS errors. Some places in the website inspector, it was reporting a CORS error to http and other places https. I had to call my web hosting service and get them to fix the problem on their end. Well, I had to call them several times. They got the secure part fixed, but then I had to type the prefix "https://" into the address bar on my iPhone to get the calendar to show. Otherwise, it just went to my homepage. Eventually my web server provider did get this fixed on their end. Apparent even when everything is on one host Safari and iOS require it. That is, I not sure that I needed an ".htaccess" file for Windows or Linux.
cat upload_proton_calendars.expect
#!/usr/bin/expect
# -- -- -- --- upload_proton_calendars -- -- -- -- -- -- -- -- --
#
# updated on 9-30-2025
#
# This script uploads my four proton calendars:
# 1) full_view_busy.ics, 2) full_view_free.ics,
# 3) full_view_wife.ics and 4) limited_view_busy.ics
# to Eleix.com at Host Gator.
#
# Dependencies: This script depends on "expect":
# sudo apt install expect
#
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# Log into the my website provider
spawn sftp rayfranco@108.167.140.94
expect "password:"
send "Redacted\n"
expect "sftp>"
# uploadd full_view_busy.ics and full_view_free.ics
send "cd franco.ms/cal\n"
expect "sftp>"
send "put ics/index.html\n"
expect "sftp>"
send "cd ics\n"
expect "sftp>"
send "put ics/full_view_busy.ics\n"
expect "sftp>"
send "put ics/full_view_free.ics\n"
expect "sftp>"
send "put ics/full_view_wife.ics\n"
expect "sftp>"
# upload limit_view_busy.ics
send "cd ../../calendar\n"
expect "sftp>"
send "put ics/index.html\n"
expect "sftp>"
send "cd ics\n"
expect "sftp"
send "put ics/limited_view_busy.ics\n"
expect "sftp>"
# quit
send "exit\n"
expect eof
According to Proton's website, if you share the calendar link with someone, they can import it into their calendar app and subscript to your calendar, which means they get updates. Is there some way to do this by code?
Be used to save all this code in a tar file on a NAS and/or on my website or on Proton's secure drive.
Proton has two Linux VPN apps. The main app has a GUI, and the CLI app has limited functionality.
Neither app supports logging in with a hardware security key. They are working on this.
The GUI app supports Debian and Ubuntu, but not the Raspberry Pi OS.
Although not officially supported, the CLI app appears to work with the Raspberry Pi OS.
Proton's VPN servers also support the official third party apps for OpenVPN and WireGuard.
If you have a paid Proton VPN plan, you can now contact support via live chat.
As of November 14, 2025, the latest release of Proton VPN Linux CLI is 0.1.2 [1]. As of January 4, 2026, the latest version is 0.1.3.
It currently only has five commands:
Usage: protonvpn [OPTIONS] COMMAND [ARGS]...
____ _ __ ______ _ _
| _ \ _ __ ___ | |_ ___ _ __ \ \ / / _ \| \ | |
| |_) | '__/ _ \| __/ _ \| '_ \ \ \ / /| |_) | \| |
| __/| | | (_) | || (_) | | | | \ V / | __/| |\ |
|_| |_| \___/ \__\___/|_| |_| \_/ |_| |_| \_| 0.1.3
Options:
-v, --verbose Show detailed output during command execution
-h, --help Show this message and exit.
Commands:
signin Sign in with Proton VPN credentials
signout Disconnect and remove credentials
info Display your Proton VPN account information
connect Connect to Proton VPN
disconnect Disconnect from Proton VPN
NEED HELP?
Report issues: https://protonvpn.com/support-form
With the free version, you cannot specify the country or city.
You cannot specify whether to use OpenVPN or WireGuard. When connected, there is a new Proton VPN entry under WireGuard. So, it is currently using WireGuard.
You cannot specify a kill switch.
You cannot whitelist IP addresses or ranges, but it does allow you to ssh and vpn to IP addresses on your subnet.
It appears to not use nftables nor iptables. Although at one time I saw a reference to nw chains in nftables.
I did face one problem with the Raspberry Pi OS. When signing in, it prompted me to enter a password for the keyring. Just leave the password blank and confirm that this is what you want, and it will not prompt you again for the keyring password [].
You sigin with:
protonvpn signin ray@franco.com
It prompts you for a password.
Enter your password, and enter:
protonvpn connect
You should now be connected to a server.
To disconnect and signout:
protonvpn disconnect
protonvpn signout
For some reason, the "protonvpn connect" command does not work over ssh. However, it does appear to work over VNC.
It also does not work if you are root: "sudo sh" or sudo protonvpn signin ray@franco.com.
According to Proton's technical support, protonvpn requires the gnone keyring. The Raspberry Pi OS does use the gonone keyring.
In theory, I should be able to write a cronjob script that connects at boot, and a script at /usr/lib/systemd/system-shutdown/ for shutting down or rebooting [3-6].
I created two script files:
cat protonvpn.exp
#!/usr/bin/expect -f
set password "redacted"
#exp_internal 1
spawn protonvpn signin ray@franco.ms
expect "Password:"
send $password\n
expect "$"
spawn protonvpn connect
expect "$"
expect eof
exit
and
cat test.exp
#!/usr/bin/expect -f
set password "redacted"
#exp_internal 1 # remove 1st # for troubleshooting
spawn protonvpn signin ray@franco.ms
expect "Password:"
send $password\r
expect {
"$ " { send_user "'$ ' prompt detected\n" }
"# " { send_user "'# ' prompt detected\n" }
eof { send_user "eof detected\n"}
timeout { send_user "expected timed out\n"}
}
spawn protonvpn connect
expect {
"$" { send_user "'$' prompt detected\n" }
"#" { send_user "'# ' prompt detected\n" }
eof { send_user "eof detected\n"}
timeout { send_user "expected timed out\n"}
}
expect {
"$ " { send_user "'$ ' prompt detected\n" }
"# " { send_user "'# ' prompt detected\n" }
eof { send_user "eof detected\n"}
timeout { send_user "expected timed out\n"}
}
Both script files will work, but they will not work if called from crontab, and it does not matter if you are root or not.
To disconnect and signout you can just use a bash script:
cat outsign.sh
#!/usr/bin/bash
protonvpn disconnect
protonvpn signout
Again the script will not work if you are root or over ssh or via a crontab.
Proton's VPN servers do support official third parts apps OpenVPN and WireGuard. To use these third parts apps, you must select a particular Proton VPN server and download a configuration file from Proton.
The disadvantages include:
As of 1/2/2026, Proton does not allow you to login to:
with a hardware security key such as Yubico.
You can however, log into https://account.proton.me/vpn.
Most of the links in the GUI app start with https://www.protonvpn.com. In addition, most of the links to download a configuration file for OpenVPN and WireGuard also start with https://www.protonvpn.com. However, you may be able to get to this page via https://account.proton.me/vpn.
As of December 24, 2023 [3], the GUI Linux app for Proton VPN is not compatible with the Raspberry Pi OS.
You can use the Command Line Interface (cli) Linux app, but it is still on version 3, while the GUI is on version 4. The current cli Linux app does not have an auto-connect feature, while the GUI version does offer auto-connect.
You have manually login:
protonvpn-cli login your_user_name
You then have to connect:
protonvpn-cli connect or just c
A console window will pop up, and you have to select the server country. Then, you must wait for almost one minute in the US, for the app to pole all the severs in the country to determine their availability and load. Some of the servers can stream videos and others cannot. After you select a sever, it will prompt you on whether to use UDP or TCP. Finally, it will connect and work as it should.
To disconnect:
protonvpn-cli disconnect or just d
To logout:
protonvpn-cli logout
To get help:
protonvpn-cli --help or -h
Proton's website says "Our new (v4) Linux doesn’t yet support a command line tool"[4]. Hopefully, this feature will be coming. According to a response in December 2023, at protonmail.uservoice.com, the cli version is planned.