This script sets up a Let's Encrypt certificate on an Nginx webserver for use in a Docker environment. It only takes a minute.
You can read more about it here: https://www.joncom.be/projects/docker-nginx-letsencrypt-setup/
- Ensure your DNS is already configured and propagated.
- Ensure docker and docker compose are installed on your server.
- Run the following line to copy the
certbot.py
andcertbot.json
files from this repo to your server.curl -OO https://raw.githubusercontent.com/joncombe/docker-nginx-letsencrypt-setup/main/{certbot.json,certbot.py}
- Edit
certbot.json
. You likely only need to edit thedomain
andemail
values. - Run the script:
python3 certbot.py
- Add the following lines to your crontab so that certificates automatically renew:
0 0,12 * * * docker compose run --rm certbot renew 5 0 * * * docker exec webserver nginx -s reload
- All done. Wasn't that easy?
There is an excellent blog post on this topic, HTTPS using Nginx and Let's Encrypt in Docker, which has helped me many times (thank you, Nathanaël). This script goes through very similar steps to what is described in that blog post but in an automated way.
This has been tested on Ubuntu servers. I think it will work on any Linux environment with Docker installed but I'd appreciate your feedback on that.
- It creates a
docker-compose.yml
withnginx
andcertbot
containers. - It creates a temporary
nginx.conf
file with enough configuration for Certbot to do its magic. - It fetches the certificates from Let's Encrypt.
- It creates a new copy of
nginx.conf
with all the settings you need to serve your website using SSL, and also redirects non-SSL traffic to the SSL version. - It leaves you with two files you can edit to suit your project:
docker-compose.yml
: Be careful not to remove any of the 3 lines in thevolumes
section. Add as many other lines as you like, but keep the ones generated for you here intact.nginx.conf
: You probably don't need to touch the port 80 section at all (thelocation /.well-known/acme-challenge/
section must remain so the certificates can renew). You can edit the port 443 section as you like but don't touch thessl_certificate
andssl_certificate_key
lines.
{
"domain": "example.com",
"email": "certbot@example.com",
"legacy_compose": false,
"nginx_image": "nginx:1.23.2-alpine",
"volume_prefix": "./data"
}
domain
should be the FQDN of your website, e.g. en.wikipedia.orgemail
is the email address you provide to Let's Encryptlegacy_compose
whentrue
when you use the v1 syntax of docker compose, i.e.docker-compose
with the hyphen. Set tofalse
if you use the moderndocker compose
without the hyphen.nginx_image
is the name of the nginx image to use. Leaving it the default value will be fine for most of you.volume_prefix
is the local path to the location your persisted docker data files are stored. If you are not sure what to do here, leave it as it is. [TODO: improve how to explain this, my terminology has failed me]
docker-compose.yml
version: '3'
services:
webserver:
image: nginx:1.23.2-alpine
ports:
- 80:80
- 443:443
restart: always
volumes:
- ./data/certbot/conf/:/etc/nginx/ssl/:ro
- ./data/certbot/www:/var/www/certbot/:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
certbot:
image: certbot/certbot:latest
volumes:
- ./data/certbot/conf/:/etc/letsencrypt/:rw
- ./data/certbot/www/:/var/www/certbot/:rw
nginx.conf
server {
listen 80;
listen [::]:80;
server_name _;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://example.com$request_uri;
}
}
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
server_name _;
server_tokens off;
ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;
location / {
if ( $host !~* ^(example.com)$ ) {
return 444;
}
# you will likely want to replace the following line with
# your own configuration
root /usr/share/nginx/html;
}
}
The cronjobs work together to keep your certificates up-to-date. There are two parts to this:
-
Firstly, the
certbot renew
command tells Certbot to attempt to renew your certificates. If your current certificates are sufficiently close to expiry, it will update them with new ones.Unfortunately, this alone is not enough. We also need Nginx to
reload
its configuration so it can begin serving the new certificates instead of the old ones.Ideally, we'd use the Certbot
--post-hook
here, which runs a command or script after a successful renewal, but this isn't straightforward as we would need for our Certbot container to communicate directly with our Nginx container. Yes, it can be done, but I specifically wanted this project to be as simple as possible and so I have chosen not to do it that way at this time. Instead... -
The second command requests nginx
reload
itself at 5-past-midnight every night. This isn't elegant but it works: each time Nginx reloads, it will use the newest certificates it has available. There is no downtime whenreload
is called.
Be aware, running this next command assumes you agree to the Let's Encrypt Subscriber Agreement. See: https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf. If you do not agree to these rules, please do NOT use this script.