This drives me nuts so I more or less favour cloud builds now to get away from having to setup HTTPS. The problem is, its just so necessary.
So I am documenting here how to do this using docker compose, nginx and certbot. One resource that helped me greatly was this excellent tutorial:
https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71
So here is how we do it:
You are going to need somewhere to host this. I used Linode.
Once you have a cheap instance spun up (Ubuntu 20ish should do), run the following:
sudo apt update && sudo apt upgrade -y
sudo apt install docker-io docker-compose
This will get you the magical docker.
Now, create yourself a docker-compose.yml
that looks like this:
version: "3"
services:
yourapplication:
container_name: yourapplication
image: yourapplication/yourapplication:latest
nginx:
image: nginx
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
- ./nginx.conf:/etc/nginx/conf.d/default.conf
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
ports:
- "80:80"
- "443:443"
certbot:
image: certbot/certbot
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
There is a lot going on here. I am assuming you have some application you want to give SSL to. So replace yourapplication
with that. Expose port 80 or whatever port you are going to tell nginx to forward requests to.
./nginx.conf:/etc/nginx/conf.d/default.conf
The above line maps a file in your current directory to a directory in the nginx container. Lets make that file!
sudo nano nginx.conf
Paste in the following:
server {
listen 80;
server_name YOURDOMAIN;
location / {
return 301 https://$host$request_uri;
}
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name YOURDOMAIN;
ssl_certificate /etc/letsencrypt/live/YOURDOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOURDOMAIN/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass "http://YOURAPPLICATIONCONTAINERNAME:THEPORTYOUEXPOSED";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
error_page 500 502 503 504 /50x.html;
}
So basically you are saying (I think)
Hey, nginx, if anyone navigates to YOURDOMAIN using http, redirect it to https. And if anyone shows up there, you can find the SSL stuff in this location to provide a cert and forward that to the docker application at http://YOURAPPLICATIONCONTAINERNAME:THEPORTYOUEXPOSED
Justin Lillico
There is a good chance I am misunderstanding this, but I had a crack!
So as the tutorial I am basing this on mentioned, there is a bit of a chicken or egg situation in that nginx needs the cert to start and the cert needs nginx to be obtained. So they have this neat little script to take care of business:
#!/bin/bash
if ! [ -x "$(command -v docker-compose)" ]; then
echo 'Error: docker-compose is not installed.' >&2
exit 1
fi
domains=(example.org www.example.org)
rsa_key_size=4096
data_path="./data/certbot"
email="" # Adding a valid address is strongly recommended
staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits
if [ -d "$data_path" ]; then
read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
exit
fi
fi
if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
echo "### Downloading recommended TLS parameters ..."
mkdir -p "$data_path/conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
echo
fi
echo "### Creating dummy certificate for $domains ..."
path="/etc/letsencrypt/live/$domains"
mkdir -p "$data_path/conf/live/$domains"
docker-compose run --rm --entrypoint "\
openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
-keyout '$path/privkey.pem' \
-out '$path/fullchain.pem' \
-subj '/CN=localhost'" certbot
echo
echo "### Starting nginx ..."
docker-compose up --force-recreate -d nginx
echo
echo "### Deleting dummy certificate for $domains ..."
docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo
echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
domain_args="$domain_args -d $domain"
done
# Select appropriate email arg
case "$email" in
"") email_arg="--register-unsafely-without-email" ;;
*) email_arg="--email $email" ;;
esac
# Enable staging mode if needed
if [ $staging != "0" ]; then staging_arg="--staging"; fi
docker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echo
echo "### Reloading nginx ..."
docker-compose exec nginx nginx -s reload
I know its a monster, so you can use the below command to create it on your linux box without copy and pasting:
curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
In any case, pay attention to these lines:
domains=(example.org www.example.org)
email="" # Adding a valid address is strongly recommended
Add your email and a space delimited list of the domains you are verifying. You are probably only doing one and that is okay!
Now, make the file executable and run it!
chmod +x init-letsencrypt.sh
./init-letsencrypt.sh
Once it has done its thing, you should be ready to fully launch this sucker!
docker-compose up -d
Test all is well. If I have messed anything up, let me know!