March 24, 2019

the web stack

build nginx with amplify: https://github.com/nginxinc/docker-nginx-amplify

git clone https://github.com/nginxinc/docker-nginx-amplify.git
cd docker-nginx-amplify
docker build -t nginx-amplify .

the compose file for nginx, certbot & ghost

version: '3'
services:

  nginx:
   restart: unless-stopped
   image: nginx-amplify
   container_name: nginx
   environment:
    API_KEY: aeaeaeae
    AMPLIFY_IMAGENAME: my-service-123
   volumes:
    - /volume1/blender-share/Docker/web/certbot/etc/letsencrypt:/etc/letsencrypt # certs
    - /volume1/blender-share/Docker/web/certbot/var/lib/letsencrypt:/var/lib/letsencrypt # acme challenge files 
    - /volume1/blender-share/Docker/web/nginx/nginx.conf:/etc/nginx/nginx.conf
    - /volume1/blender-share/Docker/web/nginx/conf.d:/etc/nginx/conf.d
   ports:
    - 4005:80
    - 4006:443

  certbot:
   container_name: certbot
   image: certbot/certbot
   depends_on:
    - nginx
   volumes:
    - /volume1/blender-share/Docker/web/certbot/etc/letsencrypt:/etc/letsencrypt # certs
    - /volume1/blender-share/Docker/web/certbot/var/lib/letsencrypt:/var/lib/letsencrypt # acme challenge files 
   entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

  ghost-blog-davidpoynerdotcom:
   restart: unless-stopped
   image: ghost:latest
   container_name: davidpoynernet
   environment:
    NODE_ENV: production
    url: http://davidpoyner.com
   ports:
    - 2368:2368
   volumes:
    - /volume1/blender-share/Docker/web/ghost:/var/lib/ghost/content

and the nginx configuration

  • forcing https through a 301 redirect
  • loading ssl settings for an a+ rating on qualys ssl labs
  • using oscp for certificate validation
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;  
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
	include /etc/nginx/conf.d/stub_status.conf;
	include /etc/nginx/mime.types;

	log_format  main_ext  '$remote_addr - $remote_user [$time_local] "$request" '
		'$status $body_bytes_sent "$http_referer" '
		'"$http_user_agent" "$http_x_forwarded_for" '
		'"$host" sn="$server_name" '
		'rt=$request_time '
		'ua="$upstream_addr" us="$upstream_status" '
		'ut="$upstream_response_time" ul="$upstream_response_length" '
		'cs=$upstream_cache_status' ;

	server {
		listen 80 default_server;
		server_name  _;
		location / {
			root   /usr/share/nginx/html;
			index  index.html index.htm;
		}
		error_page   500 502 503 504  /50x.html;
		location = /50x.html {
			root   /usr/share/nginx/html;
		}
	}

	server {
		listen 80;
		access_log /var/log/nginx/access.log  main_ext;
		server_name davidpoyner.com www.davidpoyner.com blendernet.duckdns.org www.blendernet.duckdns.org;
		return 301 https://$host$request_uri;
	}

	server {
		server_name davidpoyner.com www.davidpoyner.com;
		listen 443 ssl;
		ssl_certificate /etc/letsencrypt/live/davidpoyner.com/fullchain.pem;
		ssl_certificate_key /etc/letsencrypt/live/davidpoyner.com/privkey.pem;
		ssl_trusted_certificate /etc/letsencrypt/live/davidpoyner.com/chain.pem;
		include /etc/nginx/conf.d/ghost-blog.conf;
		include /etc/nginx/conf.d/acme-challenge.conf;
		include /etc/nginx/conf.d/perfect-forward-secrecy.conf;
	}
	
	server {
		server_name blendernet.duckdns.org www.blendernet.duckdns.org;
		listen 443 ssl;
		ssl_certificate /etc/letsencrypt/live/blendernet.duckdns.org/fullchain.pem;
		ssl_certificate_key /etc/letsencrypt/live/blendernet.duckdns.org/privkey.pem;
		ssl_trusted_certificate /etc/letsencrypt/live/blendernet.duckdns.org/chain.pem;
		include /etc/nginx/conf.d/ghost-blog.conf;
		include /etc/nginx/conf.d/acme-challenge.conf;
		include /etc/nginx/conf.d/perfect-forward-secrecy.conf;
	}
}

the ghost reverse proxy config

location / {
	proxy_pass http://10.2.1.3:2368;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Forwarded-Proto $scheme;
}

the acme-challenge config

location ^~ /.well-known/acme-challenge/ {
	allow all;
	root /var/lib/letsencrypt/;
	default_type "text/plain";
	try_files $uri =404;
}

and the pfs config

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
server_tokens off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_dhparam /etc/nginx/conf.d/dhparams2048.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;