SSL/TLS (HTTPS) deployment guide

Author:

Zhenyu Yang <yangzhenyu@sust.edu.cn>

Last updated:

Jan 24, 2026

This document describes how HTTPS is implemented for the Classroom Manager production deployment using Nginx as the TLS termination point and reverse proxy in a Docker Compose stack.

Architecture Overview

In production, a dedicated Nginx container is responsible for:

  • Terminating TLS on port 443 (HTTPS) and serving the certificate and private key.

  • Serving the built frontend as static files from /usr/share/nginx/html with SPA routing support via try_files.

  • Reverse-proxying backend requests (Django/Gunicorn) to the internal upstream backend:8000.

  • Forwarding common application paths to the backend: /api/, /media/, /admin/, and /static/.

From the Compose perspective:

  • The Nginx service publishes host ports 80 and 443.

  • Nginx mounts the production config and the certificate directory as read-only volumes.

  • Nginx depends on the backend container and joins the same Docker network for service discovery.

Files and Paths

Nginx config

Nginx loads the production virtual host config from:

  • Host path: ./nginx/nginx.conf

  • Container path: /etc/nginx/conf.d/default.conf (read-only)

Certificate directory

Certificates are expected at:

  • Host path: ./nginx/certs/

  • Container path: /etc/nginx/certs/ (read-only)

And referenced by Nginx as:

  • /etc/nginx/certs/fullchain.pem (certificate chain)

  • /etc/nginx/certs/privkey.pem (private key)

Prerequisites

  • You must have a valid TLS certificate + private key pair available as:

    • ./nginx/certs/fullchain.pem

    • ./nginx/certs/privkey.pem

  • Ensure file permissions are appropriate on the host (private key should be restricted):

    • chmod 600 ./nginx/certs/privkey.pem

    • chmod 644 ./nginx/certs/fullchain.pem

  • Update server_name in the Nginx config to match your real domain.

Certificate Provisioning

Option B: Self-signed certificate (development / internal testing only)

Self-signed certs cause browser warnings and should not be used for public production, but they are useful for quick functional validation:

mkdir -p ./nginx/certs
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
  -keyout ./nginx/certs/privkey.pem \
  -out ./nginx/certs/fullchain.pem \
  -subj "/CN=localhost"

Nginx TLS Configuration (Current Behavior)

TLS listener

The server block listens on 443 with SSL enabled.

Certificates

Nginx loads the certificate chain and key from the mounted cert directory.

Protocols and session settings

The config enables TLS 1.2 and TLS 1.3, uses an SSL session cache, sets session timeout, and disables session tickets.

Routing behavior

  • / serves the SPA frontend from /usr/share/nginx/html and falls back to /index.html for client-side routing.

  • /api/, /media/, /admin/ are reverse-proxied to the upstream and include standard forwarding headers (including X-Forwarded-Proto).

  • /static/ is also proxied to the backend.

Hardening and Production Recommendations

Add an HTTP (port 80) redirect to HTTPS

If port 80 is exposed, add a separate server block to redirect all HTTP traffic to HTTPS:

server {
   listen 80;
   server_name example.com;
   return 301 https://$host$request_uri;
}

Use a real domain and consider HSTS

Replace server_name localhost; with your actual domain. After you confirm HTTPS is stable, consider adding HSTS:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Only enable HSTS when you are sure HTTPS works correctly for your domain.

Validate Django settings behind a reverse proxy

Because Nginx forwards X-Forwarded-Proto, ensure Django is configured to trust that header so it correctly detects secure requests and generates HTTPS URLs. Common settings include:

  • SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

  • CSRF_TRUSTED_ORIGINS = ["https://example.com"]

  • SESSION_COOKIE_SECURE = True

  • CSRF_COOKIE_SECURE = True

Operational Checklist

Before go-live

  • Confirm certificate files exist:

    • ./nginx/certs/fullchain.pem

    • ./nginx/certs/privkey.pem

  • Confirm Nginx loads cleanly:

docker compose -f docker-compose.prod.yml exec nginx nginx -t
  • Confirm HTTPS reachability:

curl -vkI https://YOUR_DOMAIN/
curl -vkI https://YOUR_DOMAIN/api/

After go-live

  • Enable automatic certificate renewal (if using ACME).

  • Reload Nginx after renewal.

  • Periodically verify TLS and headers from an external network.