Nginx reverse proxy with SSL
Secure multiple web services behind an Nginx reverse proxy with automatic Let's Encrypt SSL certificates – entirely in Docker.
Requirements
- Linux server (Ubuntu 22.04/24.04 recommended)
- Domain with a DNS A record pointing to the server IP
- Root or sudo access (Docker is installed in Step 1)
- Ports 80 and 443 reachable from the internet (open them in your firewall)
Step 1: Install Docker
# Install Docker (official method) curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Log out and back in so the group change takes effect # Check the Docker Compose plugin docker compose version
Step 2: Create the project structure
mkdir -p ~/reverse-proxy/{nginx/conf.d,certbot/{conf,www}}
cd ~/reverse-proxyStep 3: Create the Docker Compose file
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
restart: unless-stopped
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h; done'"Step 4: HTTP-only configuration for the ACME challenge
Nginx refuses to start if a configuration points to certificate files that do not exist yet. So start with port 80 only – this is enough for Let's Encrypt to verify your domain:
# nginx/conf.d/default.conf
server {
listen 80;
server_name deine-domain.de;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 200 'ok';
}
}# Start Nginx (port 80 only for now) docker compose up -d nginx # Check: should return "ok" curl http://deine-domain.de
Step 5: Request the SSL certificate
# Issue the certificate via the webroot of the running Nginx docker compose run --rm --entrypoint certbot certbot certonly \ --webroot -w /var/www/certbot \ -d deine-domain.de \ --email deine@email.de \ --agree-tos --no-eff-email # Check: the certificate files now exist sudo ls certbot/conf/live/deine-domain.de/
--entrypoint certbot is required because the Compose file replaces the entrypoint of the certbot service with the renewal loop.
Step 6: Enable HTTPS and the reverse proxy
Only now that the certificate exists, replace default.conf with the full configuration. Replace dein-dienst:3000 with the service name and port of your app (the container must run in the same Compose project or Docker network).
# nginx/conf.d/default.conf
server {
listen 80;
server_name deine-domain.de;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name deine-domain.de;
ssl_certificate /etc/letsencrypt/live/deine-domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/deine-domain.de/privkey.pem;
location / {
proxy_pass http://dein-dienst:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}# Test the configuration, then reload Nginx docker compose exec nginx nginx -t docker compose exec nginx nginx -s reload # Start the certbot container for automatic renewal docker compose up -d certbot
The certbot container checks every 12 hours whether the certificate needs renewing. Nginx only uses a renewed certificate after a reload – so run the reload command above regularly, e.g. weekly via cron.
Multiple projects on one server?
Our KVM servers come with static IPs and full root access – perfect for reverse proxy setups.
Order a server