Working on HTTP locally has one persistent problem: application behaviour differs from production for everything HTTPS-dependent – Secure cookies, mixed content, HSTS, OAuth redirects. Mkcert generates locally trusted certificates without a CA, nginx-proxy automatically routes domains to containers. Together they give you an environment that looks like production.
mkcert – trusted CA in one command
# Install mkcert # macOS brew install mkcert brew install nss # for Firefox # Linux (Debian/Ubuntu) sudo apt install libnss3-tools curl -Lo mkcert https://dl.filippo.io/mkcert/latest?for=linux/amd64 chmod +x mkcert && sudo mv mkcert /usr/local/bin/ # Install local CA in trusted certificate stores mkcert -install # Installs in: system store, Firefox, Chrome/Chromium, Java # Generate certificate for local domain mkcert myshop.local "*.myshop.local" localhost 127.0.0.1 # Creates: # myshop.local+3.pem - certificate # myshop.local+3-key.pem - private key
nginx-proxy – automatic routing via Docker labels
# docker-compose.proxy.yml - run once, works for all projects
version: '3.8'
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:latest
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./certs:/etc/nginx/certs:ro
- ./vhost.d:/etc/nginx/vhost.d:ro
- ./html:/usr/share/nginx/html
restart: unless-stopped
networks:
default:
name: nginx-proxy
external: true
# Create nginx-proxy network docker network create nginx-proxy # Copy mkcert certificates to certs directory # nginx-proxy looks for: domain.crt and domain.key cp myshop.local+3.pem certs/myshop.local.crt cp myshop.local+3-key.pem certs/myshop.local.key # Start proxy docker compose -f docker-compose.proxy.yml up -d
Project docker-compose with nginx-proxy
# docker-compose.yml - Magento/PHP project
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: myshop-nginx
volumes:
- ./src:/var/www/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
environment:
- VIRTUAL_HOST=myshop.local # nginx-proxy reads this
- VIRTUAL_PORT=80
- HTTPS_METHOD=redirect # automatic HTTP to HTTPS redirect
networks:
- nginx-proxy
- internal
php:
build: ./docker/php
container_name: myshop-php
volumes:
- ./src:/var/www/html
networks:
- internal
db:
image: mariadb:10.6
container_name: myshop-db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: magento
networks:
- internal
networks:
nginx-proxy:
external: true
internal:
driver: bridge
Verification and debugging
# Check if nginx-proxy sees the containers docker logs nginx-proxy | grep -i "myshop" # Check generated nginx config docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf # Test certificate curl -v https://myshop.local/ 2>&1 | grep -E "subject|issuer|SSL" # Should show: issuer: O=mkcert development CA # Add domain to /etc/hosts if not using DNS echo "127.0.0.1 myshop.local" | sudo tee -a /etc/hosts
DDEV with mkcert
DDEV since version 1.21 configures mkcert automatically during ddev start – if mkcert is installed, DDEV generates the certificate and registers the CA. Nothing manual needed. The nginx-proxy approach above is for projects without DDEV or when you need a single proxy for multiple different stacks (PHP, Node, Python) running simultaneously.
Certificates are valid for 825 days (the browser-imposed limit for trusted CA certificates). A renewal cron: mkcert -install && mkcert *.myshop.local myshop.local && cp ... – sufficient once every two years.
