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.

Top comments (0)