DEV Community

Cover image for πŸš€ A Complete Guide to Deploying SSL-Enabled Django in Docker With Nginx, PostgreSQL & Redis
Bharat Solanke
Bharat Solanke

Posted on

πŸš€ A Complete Guide to Deploying SSL-Enabled Django in Docker With Nginx, PostgreSQL & Redis

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
Enter fullscreen mode Exit fullscreen mode

We found:

cannot load certificate "/etc/nginx/ssl/domain.crt":
PEM_read_bio_X509_AUX() failed (SSL: ... no start line)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

πŸ›  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/
Enter fullscreen mode Exit fullscreen mode

We verified they were valid:

ls -l nginx/ssl/
Enter fullscreen mode Exit fullscreen mode

All files now had valid PEM content:

  • domain.crt
  • domain.key
  • ca_bundle.crt Nginx 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;
    }
}

Enter fullscreen mode Exit fullscreen mode

🐳 6. Updating Docker for HTTPS
We exposed port 443:

nginx:
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./nginx/ssl:/etc/nginx/ssl
Enter fullscreen mode Exit fullscreen mode

Then restarted:

docker-compose down
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Nginx was now correctly listening on port 443.
But… HTTPS was still unreachable.

🧱 7. Host-Level Firewall Check
We checked:

sudo ufw status
Enter fullscreen mode Exit fullscreen mode

Result: inactive.
Next, iptables:

sudo iptables -t nat -L -n
Enter fullscreen mode Exit fullscreen mode

Docker’s NAT rules were correct:

DNAT tcp dpt:443 β†’ container_ip:443
Enter fullscreen mode Exit fullscreen mode

So the machine itself wasn’t blocking port 443.

πŸ“‘ 8. External Network Test

curl -Iv https://<server-ip>
Enter fullscreen mode Exit fullscreen mode

Result:

Trying <server-ip>:443...
(hangs forever)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Response:

HTTP/2 200
server: nginx
Enter fullscreen mode Exit fullscreen mode

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

  1. SSL files must contain valid PEM data Corrupted or empty certificates cause silent failures.
  2. Use a complete certificate chain Both:
domain.crt
ca_bundle.crt
Enter fullscreen mode Exit fullscreen mode
  1. Docker must expose HTTPS explicitly Exposing only port 80 is not enough.
  2. Never forget cloud firewalls Your VM might be open but the provider might still be blocking ports.
  3. 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)