Overview
This guide shows how to run NGINX with HTTPS (SSL/TLS) on your local computer using Docker Compose. You’ll generate a local certificate, wire it into NGINX, and verify the setup with curl and a browser.
What you’ll get:
- A minimal docker-compose.yml using nginx:alpine
- An nginx.conf with HTTP→HTTPS redirect and HTTP/2
- Two certificate options: trusted (mkcert) or self-signed (OpenSSL)
Prerequisites
- Docker and Docker Compose installed
- Terminal access
- Optional: mkcert (recommended for trusted local certs)
Quickstart (TL;DR)
- Create project structure and files.
- Generate a certificate (mkcert preferred) into ./certs.
- Start NGINX with docker compose.
- Visit https://localhost.
Minimal Working Example
Directory layout:
project/
├─ docker-compose.yml
├─ nginx/
│ └─ conf.d/
│ └─ default.conf
├─ certs/
│ ├─ localhost.crt
│ └─ localhost.key
└─ html/
└─ index.html
docker-compose.yml:
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs/localhost.crt:/etc/nginx/certs/localhost.crt:ro
- ./certs/localhost.key:/etc/nginx/certs/localhost.key:ro
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
nginx/conf.d/default.conf:
# Redirect HTTP to HTTPS
server {
listen 80;
server_name localhost;
return 301 https://$host$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name localhost;
ssl_certificate /etc/nginx/certs/localhost.crt;
ssl_certificate_key /etc/nginx/certs/localhost.key;
# Reasonable defaults
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
html/index.html:
<!doctype html>
<meta charset="utf-8">
<title>NGINX Local HTTPS</title>
<h1>It works over HTTPS!</h1>
Generate Local Certificates
Choose one option.
Option A: mkcert (trusted local certs; recommended)
- Install mkcert and its local CA once on your machine.
- Generate certs for localhost.
# From project root
mkdir -p certs
mkcert -install
mkcert -cert-file certs/localhost.crt -key-file certs/localhost.key localhost 127.0.0.1 ::1
Option B: OpenSSL (self-signed; browser warning)
mkdir -p certs
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout certs/localhost.key -out certs/localhost.crt \
-days 365 -subj "/CN=localhost"
Run It
# Start
docker compose up -d
# Check logs
docker compose logs -f nginx
# Test with curl (mkcert: should be trusted; OpenSSL: expect warning)
curl -vk https://localhost
Open a browser at https://localhost. If you used mkcert, the page should load without a certificate warning.
Step-by-Step Explanation
- Create the project structure shown above.
- Put the compose file, nginx config, and index.html in place.
- Generate certificates with mkcert or OpenSSL into ./certs.
- docker compose up -d to start the container.
- NGINX listens on 80 and 443, redirects HTTP to HTTPS, and serves html/ over TLS.
- Update content in html/ as needed; container serves changes immediately.
Using a Custom Local Domain (optional)
If you want https://app.localhost or https://myapp.local:
- For app.localhost, most systems resolve to 127.0.0.1 automatically.
- For custom names (e.g., myapp.local), add to your hosts file and include the name in your cert SANs.
Hosts entry example:
127.0.0.1 myapp.local
Generate cert including the name:
mkcert -cert-file certs/localhost.crt -key-file certs/localhost.key \
localhost 127.0.0.1 ::1 myapp.local
Update server_name accordingly in nginx config if you restrict to specific names.
Common Pitfalls and Fixes
- Browser trust warnings: Use mkcert; self-signed certs will warn. Importing your own CA requires OS steps.
- Rootless Docker on Linux cannot bind to 80/443: Map high ports instead, e.g. 8080:80 and 8443:443, then use https://localhost:8443.
- SELinux denials (Fedora/RHEL): Add :Z or :z to bind mounts, e.g. ./html:/usr/share/nginx/html:ro,Z.
- Wrong line endings on Windows: Ensure nginx config uses LF, not CRLF.
- Certificate/key path mismatch: Make sure paths in nginx config match mounted file paths.
- Reload after cert updates: docker exec -it <container> nginx -s reload or restart the service.
- Key permissions: Keep private keys outside version control; mount as :ro.
Performance Notes
- Use nginx:alpine to minimize image size and startup time.
- Enable HTTP/2 (done) for multiplexing and better local dev performance.
- Keep config small; avoid expensive logging in tight loops during local testing.
- For larger static sites, enable sendfile on (default in main config) and keep files on a local volume for fast reads.
- If you containerize backend services too, prefer a user-defined bridge network and upstreams by service name to avoid extra hops or DNS churn.
Variations
- Multiple server blocks: Add more server blocks for additional local domains, each with its own cert or a SAN cert.
- Reverse proxy to a backend: Add an upstream and proxy_pass to http://backend:port; include proxy_set_header Host and X-Forwarded-* headers.
Example snippet (proxying to a backend service ‘api’):
upstream api_upstream { server api:3000; }
server {
listen 443 ssl http2;
server_name localhost;
ssl_certificate /etc/nginx/certs/localhost.crt;
ssl_certificate_key /etc/nginx/certs/localhost.key;
location /api/ {
proxy_pass http://api_upstream/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Tiny FAQ
- Can I avoid the HTTPS warning without mkcert? Only by trusting a CA that issued your cert. mkcert installs a local CA for this purpose.
- How do I renew certificates? Re-run mkcert or OpenSSL and reload NGINX. Automate via a small script.
- Do I need separate certs per domain? You can use one SAN cert covering multiple names (localhost, 127.0.0.1, ::1, myapp.local).
- How do I stop everything? docker compose down.
Cleanup
docker compose down -v
rm -rf certs html nginx
This removes containers, volumes, and local files (be careful if you changed paths).