Let's Encrypt (Beta) on Raspberry Pi (and Ubuntu Linux)
Published 2015-6-23Watch on YouTube: youtu.be/IjL3D9km3II
Updated for Let's Encrypt v2
Dec 5 Update
NOTE: Waiting for some of the kinks to be worked out before completing the article.
Goals:
- register a domain
- open ports to allow access to your pi
- build
letsencrypt
- use
letsencrypt
to get a valid TLS (SSL) cert for HTTPS - start a https enabled webserver
Timeline:
- Installation Time: 20 minutes (wait time)
- Configuration Time: 30 seconds
- Run Time: 30 seconds
The process is very straight-forward, but the installation takes a while (because compiling C is a slow process) and actually running the certificate registration takes longer than you'd think (because python runs very slowly on Raspberry Pi).
Step 1: Get a Let's Encrypt client
I would strongly recommend caddy, but at the time of this writing (Dec 5th) it seems to not work yet. Expect an update this week.
That leaves us with the python client at https://letsencrypt.readthedocs.org/en/latest/using.html#getting-the-code
Step 2: Install the Let's Encrypt python client
git clone https://github.com/letsencrypt/letsencrypt
pushd letsencrypt
./bootstrap/debian.sh
./letsencrypt-auto
Step 3: Get a Certificate
sudo ~/.local/share/letsencrypt/bin/letsencrypt certonly \
--agree-tos \
--email john.doe@example.com \
--domains example.com,www.example.com \
--standalone
For more options see the docs
sudo ~/.local/share/letsencrypt/bin/letsencrypt --help all
Step 4: Test your certs
TODO: I would love to tell you where the certificates go, but I'm currently blocked by issue #1228
Troubleshooting
Firewall
Both ports 80 and 443 are used by letsencrypt.
If you have a firewall (such as ufw), make sure that you allow those ports through:
sudo ufw allow http
sudo ufw allow https
Getting Certs without Restarting
You run your webserver on ports 80 and 443, right?
Yet you also have to respond to cert challenges and receive your certs on 80 and 443, right?
This presents a problem, but luckily it can be solved with HAProxy:
Port Forwarding
If you rely on port forwarding, (as is common for a Raspberry Pi on your home network) make sure you allow those ports through.
Original Post
You may also be interested in
Goals:
- register a domain
- open ports to allow access to your pi
- build
letsencrypt
- use
letsencrypt
to get a valid TLS (SSL) cert for HTTPS - start a https enabled webserver
Following the instructions at https://letsencrypt.readthedocs.org/en/latest/using.html#getting-the-code
The Easy Way
Soon caddy
will have built-in letsencrypt support
and it will also have an API that allows updating certificates and routes on-the-fly.
Install and Build letsencrypt
This process is super slow on a raspberry pi because it has to compile a metric ton
of dependencies for python
.
I think it was in the 5 to 10-minute range.
git clone https://github.com/letsencrypt/letsencrypt
pushd letsencrypt
sudo ./bootstrap/debian.sh
virtualenv --no-site-packages -p python2 venv
./venv/bin/pip install -r requirements.txt acme/ .
Grab some munchies and wait it out...
On the Raspberry Pi model B it can take quite a while.
1078.53user 56.81system 21:13.52elapsed 89%CPU (0avgtext+0avgdata 85260maxresident)k
356600inputs+491984outputs (485major+669516minor)pagefaults 0swaps
On the B+ and 2.0 there is much more memory, which is the bulk of the holdup.
And if you have a firewall, don't forget to allow port 443/tcp (and probably 80/tcp too)
sudo ufw allow http
sudo ufw allow https
Get a certificate for your Domain
sudo mkdir -p /etc/letsencrypt/
sudo vim /etc/letsencrypt/cli.ini
/etc/letsencrypt/cli.ini
:
# This is an example configuration file for developers
#config-dir = /tmp/le/conf
#work-dir = /tmp/le/conf
#logs-dir = /tmp/le/logs
# make sure to use a valid email and domains!
email = foo@example.com
#domains = example.com,www.example.com
text = True
agree-eula = True
debug = True
# Unfortunately, it's not possible to specify "verbose" multiple times
# (correspondingly to -vvvvvv)
verbose = True
authenticator = standalone
This is also ridiculously slow on the Raspberry Pi (a minute or two).
The forthcoming node and go clients should be significantly faster (perhaps in the range of 10 to 20 seconds).
Standalone mode
sudo ./venv/bin/letsencrypt \
--email coolaj86@gmail.com \
--domains dangerpi.devices.coolaj86.com \
--authenticator standalone \
auth
For reference, on the Raspberry Pi model A this took about a minute
96.99user 1.01system 1:52.08elapsed 87%CPU (0avgtext+0avgdata 29296maxresident)k
2176inputs+160outputs (0major+17006minor)pagefaults 0swaps
Manual mode
sudo ./venv/bin/letsencrypt \
--email coolaj86@gmail.com \
--domains dangerpi.devices.coolaj86.com \
--authenticator manual \
--dvsni-port 443 \
auth
Other userful options
--agree-eula
--text
--authenticator manual
--authenticator standalone
You will need to specify manual or automatic mode.
Automatic vs Manual
I've had success with both manual mode and standalone mode.
You'll have to open 443 and run as root. There's no way to specify another port as per the spec, though there are reasonable arguments to change the spec.
Minimizing Downtime for a Production WebServer
If you already have a webserver running on 443, you might want to configure happroxy to front
for that webserver and have a secondary webserver that handles SNI passes requests to *.acme.invalid
to letsencrypt
running on an alternate port using the --dvsni-port
option.
That server needs to be able to statically serve traffic to /.well-known/acme-challenge/
.
Manual-Mode One-Off Server
When you choose manual mode you will be given the instruction to run commands similar to these:
mkdir -p .well-known/acme-challenge
# write the challenge out to a file
echo -n K0haDCp1jSTnrhTFSrdQML5SjgJp1prYSoXM6Jdab5c \
> .well-known/acme-challenge/gQrWUQIHZc_dP5NsvirVlsRa
# create a self-signed root ca to serve as if it were a certificate
openssl req -new -newkey rsa:4096 -subj "/" -days 1 -nodes -x509 \
-keyout key.pem \
-out cert.pem
# create a one-off https server to issue the certificate
sudo python -c "import BaseHTTPServer, SimpleHTTPServer, ssl; \
s = BaseHTTPServer.HTTPServer(('', 443), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.socket = ssl.wrap_socket(s.socket, keyfile='key.pem', certfile='cert.pem'); \
s.serve_forever()"
You will need to do this in a new terminal window. As soon as the certificates are granted you can stop this server.
Result
You will likely have some warning messages about insecure certificates (the one-off server) and also some success messages.
If all went you can run tree /etc/letsencrypt/
and you'll see your domain in a structure like this:
Install and run tree
sudo apt-get install --yes tree
tree /etc/letsencrypt/
The output should look like this:
/etc/letsencrypt/
├── accounts
│ └── www.letsencrypt-demo.org
│ └── acme
│ └── new-reg
│ ├── coolaj86@gmail.com
│ └── keys
│ └── 0000_coolaj86@gmail.com
├── archive
│ └── dangerpi.devices.coolaj86.com
│ ├── cert1.pem
│ ├── chain1.pem
│ ├── fullchain1.pem
│ └── privkey1.pem
├── certs
│ └── 0000_csr-letsencrypt.pem
├── configs
│ └── dangerpi.devices.coolaj86.com.conf
├── keys
│ └── 0000_key-letsencrypt.pem
└── live
└── dangerpi.devices.coolaj86.com
├── cert.pem -> ../../archive/dangerpi.devices.coolaj86.com/cert1.pem
├── chain.pem -> ../../archive/dangerpi.devices.coolaj86.com/chain1.pem
├── fullchain.pem -> ../../archive/dangerpi.devices.coolaj86.com/fullchain1.pem
└── privkey.pem -> ../../archive/dangerpi.devices.coolaj86.com/privkey1.pem
The important files are the key, cert, and chain, you can read about them here:
/etc/letsencrypt/live/dangerpi.devices.coolaj86.com/
├── cert.pem # the server certificate only
├── chain.pem # the intermediate ca(s)
├── fullchain.pem # cert.pem + chain.pem
└── privkey.pem # the server's private key
Note: until the real certificates are issued in September 2015, the "happy hacker" Root CA is being issued in place of the intermediate certificate.
Note: generally speaking the order of concatonated certificates should be most-specific (least authoritative) to least-specific (most authoritative) - such as [cert, intermediate, root] and not [root, intermediate, cert]. This improves compatibility with some servers.
Note: I've seen some servers that like cat cert.pem privkey.pem > server.pem
and
some that do not like the Root CA to be in the chain or fullchain.
Testing your Certs
You can use serve-https
to quickly test your certificates straight
from the /etc/letsencrypt
.
npm install -g serve-https
sudo serve-https -p 8443 --letsencrypt-certs foo.example.com --serve-chain true
curl --insecure https://foo.example.com:8443 > chain.pem
curl https://foo.example.com:8443 --cacert chain.pem
See serve-https.
Using your Certs with Node.js
In node.js you would use these like so:
'use strict';
var fs = require('fs');
var path = require('path');
var letsetc = '/etc/letsencrypt/live/';
var defaultdomain = 'dangerpi.devices.coolaj86.com';
function getSecureContext(domainname, opts) {
if (!opts) { opts = {}; }
opts.key = fs.readFileSync(path.join(letsetc, domainname, 'privkey.pem'))
opts.cert = fs.readFileSync(path.join(letsetc, domainname, 'cert.pem'));
opts.ca = fs.readFileSync(path.join(letsetc, domainname, 'chain.pem'), 'ascii')
.split('-----END CERTIFICATE-----')
.filter(function (ca) {
return ca.trim();
}).map(function (ca) {
return (ca + '-----END CERTIFICATE-----').trim();
});
return require('tls').createSecureContext(opts);
}
//
// SSL Certificates
//
var options = {
, requestCert: false
, rejectUnauthorized: true
// If you need to use SNICallback you should be using io.js >= 1.x (possibly node >= 0.12)
, SNICallback: function (domainname, cb) {
var secureContext = getSecureContext(domainname);
cb(null, secureContext);
}
// If you need to support SPDY/HTTP2 this is what you need to work with
//, NPNProtocols: ['http/2.0', 'spdy', 'http/1.1', 'http/1.0']
, NPNProtocols: ['http/1.1']
};
// Start the server
server = https.createServer(getOpts(defaultdomain, options));
server.on('error', function (err) {
console.error(err);
});
server.listen(443, function () {
console.log('Listening');
});
server.on('request', function (req, res) {
res.end('hello');
});
By AJ ONeal
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )