Let’s Encrypt and Emby
I finally got around to setting up SSL with Let’s Encrypt on my Emby instance.
I use acme.sh
for managing my Let’s Encrypt certificates. It’s a
simple shell script that Just Works™. I use DigitalOcean for my DNS on my main
network domain, priddle.network
. acme.sh
supports DigitalOcean DNS out of
the box.
Emby’s SSL setup is a little different than a traditional web server like
Nginx. Namely, you use a .pfx
file instead of a .crt
and .key
pair. I
suppose this isn’t that uncommon, since again, acme.sh
supports it out of
the box.
A lot of setups recommend using Nginx as a reverse proxy to you regular Emby
host—which would be http://emby.priddle.network:8096
in my case. I tried
this with Plex a while back and it was a pain. You have to setup a lot of
different things to make websockets work correctly.
Emby (and Plex, which I’ve obviously moved away from) has a built-in SSL
support. You can just point it to your .pfx
file and it works. Infuse and my
other clients work perfectly.
Since I’ll have the certs, I can also setup http://emby.priddle.network
and
https://emby.priddle.network
(without ports) to redirect to the Emby server
running on SSL. We’ll get back to that in a bit.
First, install acme.sh
. They recommend running it as root, so I’ll do that.
sudo -i
curl https://get.acme.sh | sh -s email=ssl@example.com
I like getting notifications when my certs renew. I use Slack for this. For
simplicity, I just used a webhook URL. acme.sh
also supports this out of the
box.
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/xxxxxxxxx/xxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx"
acme.sh --set-notify --notify-hook slack
We need a directory to store the certs that Emby and Nginx will use. Create it with:
sudo mkdir -p /usr/local/etc/ssl/emby.priddle.network/
We’ll need a script to generate the .pfx
file from the Let’s Encrypt certs.
It’ll also need to restart Emby and Nginx. Save it as /root/reload-emby.sh
:
#!/usr/bin/env bash
set -e
/root/.acme.sh/acme.sh \
--to-pkcs12 \
--password 'real-password' \
--domain emby.priddle.network
cp /root/.acme.sh/emby.priddle.network_ecc/emby.priddle.network.pfx \
/usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.pfx
chmod 644 /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.pfx
systemctl restart emby-server.service
nginx -s reload
Don’t run this script yet. It’ll be run automatically by acme.sh
when we get the certs.
Make sure to replace real-password
with a real password. This is the
password that Emby will use to decrypt the .pfx
file. You also need to
chmod +x /root/reload-emby.sh
so acme.sh
can run it.
Next, let’s enable SSL on Emby. Go to
http://emby.local:8096/web/index.html#!/network and make sure to enable
“Allow remote connections to this Emby Server.” Enter your domain name (i.e
emby.priddle.network
) in “External
domain” and set the “Custom ssl certificate path” to
/usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.pfx
. Enter the
certificate password (i.e. “real-password”). Save these settings, but
don’t restart Emby. We’ll do that when we get the certs.
Let’s hop over to Nginx and set that up. For me, I’m only using Nginx on my
Ember server to redirect to the real Emby URLs, so I just replaced everything
in /etc/nginx/sites-enabled/default
with:
server {
listen 80;
server_name _;
return 301 https://$host:8920$request_uri;
}
server {
listen 443 ssl;
server_name _;
ssl_certificate /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.cert.pem;
ssl_certificate_key /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.key.pem;
ssl_trusted_certificate /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.fullchain.pem;
return 301 https://$host:8920$request_uri;
}
I also dropped this in /etc/nginx/nginx.conf
in the http { ... }
block:
http {
# ^ other stuff that was already there
# Disable server tokens (i.e. Nginx) in response headers
server_tokens off;
# SSL session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/nginx/dhparam.pem;
# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;
}
On my system, I had to create /etc/nginx/dhparam.pem
with:
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Test the Nginx config with:
sudo nginx -t
Now we’re ready to create the certs. See
https://github.com/acmesh-official/acme.sh/wiki/dnsapi for info on different
DNS providers. I’m using DigitalOcean, so I’ll set the DO_API_KEY
environment variable to my DigitalOcean API key.
export DO_API_KEY="key"
Next, issue the certs:
acme.sh --issue --dns dns_dgon -d emby.priddle.network
Install the certs:
acme.sh --install-cert -d emby.priddle.network \
--cert-file /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.cert.pem \
--key-file /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.key.pem \
--fullchain-file /usr/local/etc/ssl/emby.priddle.network/emby.priddle.network.fullchain.pem \
--reloadcmd '/root/reload-emby-ssl.sh'
If everything worked, you should see the certs in
/usr/local/etc/ssl/emby.priddle.network/
. The reloadcmd
will have run
/root/reload-emby.sh
after the certs were generated. Emby should have
restarted and Nginx should have reloaded.
To test run the following curl
commands:
curl -vs https://emby.priddle.network 2>&1 | grep Location
curl -vs http://emby.priddle.network 2>&1 | grep Location
curl -vs https://emby.priddle.network:8920 2>&1 | grep 'private-network-access-name'
And you should see something like:
< Location: https://emby.priddle.network:8920/
< Location: https://emby.priddle.network:8920/
< private-network-access-name: Media Server
Give it a try in your web browser to make sure everything worked.
Finally, you can test everything fully with:
acme.sh --cron --force