Skip to content

Instantly share code, notes, and snippets.

@aFelipeSP
Last active January 25, 2026 23:51
Show Gist options
  • Select an option

  • Save aFelipeSP/d83d7bbbba3556ff82eba610648f17f6 to your computer and use it in GitHub Desktop.

Select an option

Save aFelipeSP/d83d7bbbba3556ff82eba610648f17f6 to your computer and use it in GitHub Desktop.
Docker compose file to setup nginx proxy server with TLS using certbot

This is an all-in-one docker compose file to setup a nginx proxy server with TLS using certbot. This configuration tries to renew the certificates and reloads the nginx configuration every 24 hours. You just have to create a .env file next to the compose file with the server name and an email for the ACME protocol, like this:

SERVER_NAME=...
EMAIL=...

There's an external docker network called nginx-network in the compose configuration, that you can use to have access to other services running in the same network. You have to create it before running docker compose up:

docker network create nginx-network

There's a nginx server listening in port 80 that serves the ACME files and redirects any requests to the https server. You can extend this server through ./user_conf/http.conf.

There's also an nginx server listening in port 443 with all the configuration to use TLS. You can extend this server through ./user_conf/https.conf.

You can also add more servers by adding .conf files to ./user_conf/conf.d/.

You can use files from the host in the server by adding them to a folder next to the compose file called shared. This folder is in sync through a docker volume located in /nginx/shared inside the container, and you can use it to serve staticfiles or store uploaded files.

For example, you can have a ./user_conf/https.conf file like the following to serve any files in the shared folder:

location / {
    root /nginx/shared;
    try_files $uri $uri/ =404;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Url-Scheme $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host my.site.com;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
}

This way you can have a ./user_conf/https.conf file like the following to proxy all the requests to a specific service:

location / {
    proxy_pass http://<SERVICE_NAME>:<PORT>;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Url-Scheme $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host my.site.com;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
}

To start the server you can run:

docker compose up -d
services:
dhparam_job:
image: frapsoft/openssl
container_name: dhparam
entrypoint:
- /bin/sh
- -c
- |
FILE="/dhparam/dhparam-2048.pem"
if [ ! -f "$$FILE" ]; then
openssl dhparam -out /dhparam/dhparam-2048.pem 2048
fi
volumes:
- dhparam:/dhparam
proxy:
image: nginx
container_name: nginx_proxy
restart: always
init: true
networks:
- nginx-network
depends_on:
dhparam_job:
condition: service_completed_successfully
ports:
- "80:80"
- "443:443"
volumes:
- ./user_conf:/nginx/user_conf
- ./shared:/nginx/shared
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- dhparam:/etc/ssl/certs:ro
- messages:/messages:ro
- acme:/acme
command:
- /bin/sh
- -c
- |
set -e
cat <<EOF > /etc/nginx/nginx.conf
http {
server {
server_name $SERVER_NAME; listen 80; listen [::]:80;
location ~ ^/\.well-known/acme-challenge { allow all; root /acme; }
location / { return 301 https://$$host$$request_uri; }
include /nginx/user_conf/http[.]conf;
}
include /etc/nginx/https[.]conf;
include /nginx/user_conf/conf.d/*.conf;
}
events {}
EOF
cat <<EOF > /etc/nginx/httpsconf
server {
server_name $SERVER_NAME; listen 443 ssl; listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/$SERVER_NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$SERVER_NAME/privkey.pem;
server_tokens off;
ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
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;
ssl_ecdh_curve secp384r1;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8;
include /nginx/user_conf/https[.]conf;
}
EOF
FILE="/messages/cert_ready"
if [ -f "$$FILE" ]; then
cp /etc/nginx/httpsconf /etc/nginx/https.conf
fi
(
FILE="/messages/cert_ready"
if [ ! -f "$$FILE" ]; then
echo "Waiting for cert_ready"
until [ -e "$$FILE" ]; do
sleep 1
done
echo "cert_ready found"
cp /etc/nginx/httpsconf /etc/nginx/https.conf
nginx -t && nginx -s reload
fi
sleep 1m
while true; do
sleep 1d
echo "Reloading nginx..."
nginx -t && nginx -s reload
done &
) &
exec nginx -g 'daemon off;'
certbot:
image: certbot/certbot
container_name: certbot
init: true
volumes:
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- acme:/acme
- messages:/messages
depends_on:
- proxy
entrypoint:
- /bin/sh
- -c
- |
set -e
FILE=/messages/cert_ready
if [ ! -f "$$FILE" ]; then
echo "No certificate found"
certbot certonly \
--webroot \
--webroot-path /acme \
--email $EMAIL \
--agree-tos \
--no-eff-email \
-d $SERVER_NAME
echo "Certificate generated"
touch /messages/cert_ready
fi
trap exit TERM
while true; do
certbot renew
echo "Certificate renew try"
sleep 1d & wait $${!}
done
volumes:
certbot-etc:
certbot-var:
dhparam:
certbot:
messages:
acme:
networks:
nginx-network:
external: true
name: nginx-network
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment