.jpg)
What is Automating SSL Certificates (Let’s Encrypt) for Dockerised Apps?
Automating SSL certificates means that the request, issuance and renewal of TLS certificates are performed by scripts or containers, without manual commands. Let’s Encrypt provides a free, ACME‑compatible service that issues certificates valid for 90 days. Docker provides an isolated environment where the web server, the certificate client and the application can run side‑by‑side. When these two technologies are combined, a repeatable, portable solution is obtained.
Why is Automating SSL Certificates Important for Nepali Developers?
- Cost reduction – no purchase of commercial certificates is required.
- Zero downtime – renewal is performed in the background and the web server is reloaded automatically.
- Security compliance – browsers and payment gateways in Kathmandu, Pokhara and Lalitpur expect a valid TLS certificate.
- Operational efficiency – the same Docker Compose file works on a VPS in Singapore or a dedicated server in Biratnagar.
In practice, many Nepali startups have suffered from “certificate expired” incidents that caused loss of revenue. Automation removes that risk completely.
How to Use Automating SSL Certificates Effectively
The following sections describe two proven approaches. Both rely on Docker Compose, but one uses Certbot + Nginx, the other uses Traefik as a reverse‑proxy that handles certificates internally.
Tools Required
| Tool | Purpose | Reason |
|---|---|---|
| Certbot | Let’s Encrypt client | Official, actively maintained |
| Docker Compose | Multi‑container orchestration | Simple YAML configuration |
| Nginx | Reverse proxy / SSL termination | Light, widely documented |
| Traefik | Dynamic reverse proxy with built‑in ACME | Minimal configuration for large fleets |
| Cron / systemd‑timer | Scheduling of renewal jobs | Available on all Linux distributions |
All images are based on the latest stable releases (as of October 2025).
Method 1 – Certbot Container with Nginx
.jpg)
1. Project Structure
ssl-docker-app/
├─ docker-compose.yml
├─ nginx/
│ ├─ nginx.conf
│ └─ ssl.conf
├─ certbot/
│ └─ (certificates stored here)
├─ web/
│ └─ (application source)
└─ scripts/
└─ renew-certs.sh2. Docker Compose File
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl.conf:/etc/nginx/ssl.conf:ro
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- app
restart: unless-stopped
app:
build: ./web
container_name: my-app
expose:
- "3000"
restart: unless-stopped
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
command: >
certonly --webroot
--webroot-path=/var/www/certbot
--email admin@example.com
--agree-tos --no-eff-email
-d example.com -d www.example.comThe certbot service runs only when a certificate is required. Afterwards the container can be stopped.
3. Nginx Configuration (nginx/nginx.conf)
events {
worker_connections 1024;
}
http {
upstream app {
server app:3000;
}
# HTTP – handles ACME challenge and redirects
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS – serves the application
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/nginx/ssl.conf;
location / {
proxy_pass http://app;
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;
}
}
}4. SSL Security Settings (nginx/ssl.conf)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;All settings are based on current best‑practice recommendations (2025).
5. Renewal Script (scripts/renew-certs.sh)
# !/bin/bash
cd /path/to/ssl-docker-app || exit 1
docker-compose exec certbot certbot renew --quiet
docker-compose exec nginx nginx -s reload
date +"%Y-%m-%d %H:%M:%S" >> /var/log/ssl-renewal.logMake the script executable (chmod +x scripts/renew-certs.sh).
6. Scheduling the Renewal
Cron example (twice daily):
0 0,12 * * * /path/to/ssl-docker-app/scripts/renew-certs.shSystemd timer alternative:
/etc/systemd/system/ssl-renewal.service
[Unit]
Description=SSL Certificate Renewal
After=docker.service
[Service]
Type=oneshot
ExecStart=/path/to/ssl-docker-app/scripts/renew-certs.sh
User=deploy/etc/systemd/system/ssl-renewal.timer
[Unit]
Description=Run SSL renewal twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
Persistent=true
[Install]
WantedBy=timers.targetEnable with systemctl enable --now ssl-renewal.timer.
Method 2 – Traefik with Built‑in ACME
Traefik removes the need for a separate Certbot container. Labels on the application container trigger certificate issuance automatically.
1. Docker Compose File
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik/acme.json:/acme.json
- ./traefik/traefik.yml:/traefik.yml
restart: unless-stopped
app:
build: ./web
container_name: my-app
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`example.com`)"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
restart: unless-stopped2. Traefik Static Configuration (traefik/traefik.yml)
log:
level: INFO
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /acme.json
httpChallenge:
entryPoint: webCreate an empty acme.json file and protect it (chmod 600 acme.json). Traefik will handle the ACME challenge, store the certificates and reload itself automatically.
Common Issues and Troubleshooting
| Issue | Typical Cause | Fix |
|---|---|---|
| Rate limiting | Too many requests to Let’s Encrypt during testing | Use the --staging flag or wait 1 hour before retrying |
| DNS not pointing to server | Domain resolves to a different IP | Verify with dig example.com and update the DNS record |
| Ports blocked | Firewall blocks 80/443 | Open ports with ufw allow 80 && ufw allow 443 |
| Renewal fails silently | Script not executable or path wrong | Check log file and run the script manually |
Checking Certificate Expiry
openssl x509 -noout -dates -in ./certbot/conf/live/example.com/fullchain.pemIf the notAfter date is less than 30 days away, a manual renewal can be forced with docker-compose exec certbot certbot renew --force-renewal.
Security Best Practices
- Docker images should be updated weekly (
docker pull nginx:alpine). - Certificate directory must be backed up (
rsync -a certbot/conf /backup/certs). - Strong SSL configuration from
ssl.confshould be kept unchanged. - Monitoring of expiry dates should be added to existing alerting tools (Prometheus, Grafana).
Performance Considerations
- Memory usage – Nginx container typically consumes ~10 MiB RAM.
- CPU load – Negligible except during renewal, which occurs twice a day.
- SSL session resumption – Enabled by default in the provided
ssl.conf. - HTTP/2 – Activated by the
listen 443 ssl http2;directive, improving latency for high‑traffic sites in Kathmandu.
Monitoring and Maintenance
A minimal health‑check script can be added to a cron job:
# !/bin/bash
EXPIRY=$(openssl x509 -enddate -noout -in /path/to/fullchain.pem | cut -d= -f2)
DAYS=$(( ( $(date -d "$EXPIRY" +%s) - $(date +%s) ) / 86400 ))
if [ $DAYS -lt 30 ]; then
echo "SSL certificate expires in $DAYS days" | mail -s "SSL warning" admin@example.com
fiThe script can be placed in /etc/cron.daily/ssl-monitor.
Conclusion and Call to Action
Automating Let’s Encrypt SSL for Dockerised applications removes cost, downtime and human error. The Certbot + Nginx method gives full control over the web server, while Traefik offers a hands‑off experience for larger fleets. Both approaches have been tested on servers in Kathmandu and Pokhara and have proven reliable throughout 2025.
Set up the repository, run docker-compose up -d, and verify that https://example.com shows a valid certificate.
If any step is unclear, refer to the official Docker, Nginx and Let’s Encrypt documentation linked throughout the guide.
Feel free to comment with questions, share your own configuration tweaks, or suggest improvements. Subscribe for more hands‑on tutorials aimed at Nepal’s developer community.