A practical journey of debugging, configuring, and enabling HTTPS on a production server.
Deploying a secure Django application in production often looks simple on paper — until you meet the real-world challenges of certificates, Docker networking, Nginx proxying, and cloud firewalls.
This blog walks through the end-to-end process we followed to bring a Dockerized Django system live over HTTPS. It covers:
- How we diagnosed SSL misconfigurations
- Fixing invalid certificates
- Updating Docker + Nginx
- Firewall/NAT troubleshooting
- Final verification steps
This is not just a how-to; it is a real engineering story.
🧩 1. Deployment Architecture
The application stack:
- ✔ Django (served with Gunicorn)
- ✔ Nginx (reverse proxy + static/media)
- ✔ PostgreSQL
- ✔ Redis
- ✔ Celery + Flower
- ✔ Docker & docker-compose HTTP worked perfectly. HTTPS, however, would hang indefinitely and then time out.
🎯 2. The Goal
We needed to successfully:
- ✔ Install valid SSL certificates
- ✔ Configure Nginx to serve HTTPS correctly
- ✔ Expose port 443 from Docker → host machine
- ✔ Ensure firewall/NAT permitted inbound HTTPS
- ✔ Validate secure access from any browser
🔎 3. Debugging the SSL Failure
First step: check Nginx logs inside the container:
docker logs nginx
We found:
cannot load certificate "/etc/nginx/ssl/domain.crt":
PEM_read_bio_X509_AUX() failed (SSL: ... no start line)
This error meant:
👉 The certificate file existed — but its content was empty or invalid.
We checked the file:
head -n 2 nginx/ssl/domain.crt
🛠 4. Fixing the SSL Files
The correct .crt, .key, and CA bundle files were uploaded to the server using:
scp -P <port> certificate.crt ca_bundle.crt user@server:/path/to/nginx/ssl/
We verified they were valid:
ls -l nginx/ssl/
All files now had valid PEM content:
domain.crtdomain.key-
ca_bundle.crtNginx was reloaded afterward.
🧱 5. Configuring HTTPS in Nginx
We updated the configuration to:
- ✔ Redirect HTTP → HTTPS
- ✔ Serve static & media correctly
- ✔ Proxy Django with correct headers
- ✔ Load the correct certificates
Final Nginx HTTPS configuration
upstream django_app {
server web:8000;
}
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name _;
ssl_certificate /etc/nginx/ssl/domain.crt;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_trusted_certificate /etc/nginx/ssl/ca_bundle.crt;
location /static/ {
alias /usr/share/nginx/html/static/;
}
location /media/ {
alias /usr/share/nginx/html/media/;
}
location / {
proxy_pass http://django_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
🐳 6. Updating Docker for HTTPS
We exposed port 443:
nginx:
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/ssl:/etc/nginx/ssl
Then restarted:
docker-compose down
docker-compose up -d
Nginx was now correctly listening on port 443.
But… HTTPS was still unreachable.
🧱 7. Host-Level Firewall Check
We checked:
sudo ufw status
Result: inactive.
Next, iptables:
sudo iptables -t nat -L -n
Docker’s NAT rules were correct:
DNAT tcp dpt:443 → container_ip:443
So the machine itself wasn’t blocking port 443.
📡 8. External Network Test
curl -Iv https://<server-ip>
Result:
Trying <server-ip>:443...
(hangs forever)
This was the critical clue:
❌ No traffic was reaching the server
❌ Not even reaching Docker or Nginx
This meant the cloud/datacenter firewall was blocking 443.
📩 9. Requesting Cloud Team to Open Port 443
We escalated the issue and requested:
“Please enable inbound HTTPS (443) on the network firewall.
The server is listening correctly, but incoming TLS traffic is blocked.”
After the network team opened port 443…
🔥 HTTPS started working instantly.
🎉 10. Final Verification
We tested again:
curl -Iv https://<your-domain>
Response:
HTTP/2 200
server: nginx
Browser showed:
- ✔ HTTPS lock
- ✔ Valid certificate
- ✔ Full redirect from HTTP → HTTPS
- ✔ Static/media loading correctly
- ✔ Django + WebSockets running normally
Deployment was successful.
🏁 11. Key Lessons Learned
- SSL files must contain valid PEM data Corrupted or empty certificates cause silent failures.
- Use a complete certificate chain Both:
domain.crt
ca_bundle.crt
- Docker must expose HTTPS explicitly Exposing only port 80 is not enough.
- Never forget cloud firewalls Your VM might be open but the provider might still be blocking ports.
- Debug every layer To solve HTTPS issues, inspect:
- Browser
- DNS
- Cloud firewall
- Server firewall
- iptables
- Docker NAT routing
- Nginx configs
- SSL files
- Application headers Solving it required checking each of these layers.
📘 12. Conclusion
Deploying SSL in a production Docker environment is not just about placing certificate files in a folder.
It requires a complete understanding of:
- ✔ Nginx reverse proxying
- ✔ SSL termination
- ✔ Docker networking
- ✔ Cloud firewall rules
- ✔ Valid certificates
- ✔ Correct header forwarding Once all layers worked together, the system became fully HTTPS-enabled and production-ready.


