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/htmlwith SPA routing support viatry_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.confContainer 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.pemchmod 644 ./nginx/certs/fullchain.pem
Update
server_namein the Nginx config to match your real domain.
Certificate Provisioning¶
Option A: Use a trusted CA (recommended)¶
Obtain a certificate from a trusted CA (e.g., via ACME/Let’s Encrypt) on the host (or a dedicated cert
management container), then copy/symlink the resulting files into ./nginx/certs/:
Place the full chain as
./nginx/certs/fullchain.pemPlace the private key as
./nginx/certs/privkey.pem
After renewal, reload Nginx so it picks up the new files:
docker compose -f docker-compose.prod.yml exec nginx nginx -t
docker compose -f docker-compose.prod.yml exec nginx nginx -s reload
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/htmland falls back to/index.htmlfor client-side routing./api/,/media/,/admin/are reverse-proxied to the upstream and include standard forwarding headers (includingX-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.
Add modern security headers (recommended)¶
Typical baseline:
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
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 = TrueCSRF_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.