KhueApps
Home/DevOps/Docker Compose: NGINX with Local HTTPS (SSL) Setup

Docker Compose: NGINX with Local HTTPS (SSL) Setup

Last updated: October 06, 2025

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)

  1. Create project structure and files.
  2. Generate a certificate (mkcert preferred) into ./certs.
  3. Start NGINX with docker compose.
  4. 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

  1. Create the project structure shown above.
  2. Put the compose file, nginx config, and index.html in place.
  3. Generate certificates with mkcert or OpenSSL into ./certs.
  4. docker compose up -d to start the container.
  5. NGINX listens on 80 and 443, redirects HTTP to HTTPS, and serves html/ over TLS.
  6. 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).

Series: Docker

DevOps