Introduction
This setup will allow you to have multiple servers/containers accessible via a single IP address with the added benefit of a centralized generation of letsencrypt certificates and secure https (according to ssllabs ssltest). Be aware that you first need to setup a regular HTTP server in order to be able to generate your HTTPS certificates and keys. After you have generated them, you can then add your HTTPS host based configuration.
See the NGINX page for general information about Nginx, starting/stopping the service etc.
Installation
For this howto, we need three tools: NGINX, acme-client and openssl (to generate Diffie–Hellman Parameters).
apk update
apk add nginx acme-client openssl
Setup
NGINX HTTP
Global configuration
First step is to refactor our global nginx.conf
. Its target at a low traffic http server, to increase performance make changes at top level.
Contents of /etc/nginx/nginx.conf
# /etc/nginx/nginx.conf
user nginx;
worker_processes 1; # use "auto" to use all available cores (high performance)
# Configures default error logger.
error_log /var/log/nginx/error.log warn; # Log warn, error, crit, alert, emerg
events {
# The maximum number of simultaneous connections that can be opened by a worker process.
worker_connections 1024; # increase if you need more connections
}
http {
# server_names_hash_bucket_size controls the maximum length
# of a virtual host entry (ie the length of the domain name).
server_names_hash_bucket_size 64; # controls the maximum length of a virtual host entry (ie domain name)
server_tokens off; # hide who we are, don't show nginx version to clients
sendfile off; # can cause issues
# nginx will find this file in the config directory set at nginx build time
# Includes mapping of file name extensions to MIME types of responses
include mime.types;
# fallback in case we can't determine a type
default_type application/octet-stream;
# buffering causes issues, disable it
# increase buffer size. still useful even when buffering is off
proxy_buffering off;
proxy_buffer_size 4k;
# allow the server to close the connection after a client stops responding. Frees up socket-associated memory.
reset_timedout_connection on;
# Specifies the main log format.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
# Buffer log writes to speed up IO, or disable them altogether
access_log /var/log/nginx/access.log main buffer=16k;
#access_log off;
# include virtual hosts configs
include conf.d/*.conf;
}
SSL configuration
Configure a file with all SSL-parameters that we can include in the virtual hosts configs later on.
The security settings are inspired by the Mozilla SSL Configuration Generator. Please also read https://hstspreload.org for details about HSTS.
Contents of /etc/nginx/conf.d/ssl-params.inc
# secure nginx, see https://ssl-config.mozilla.org
ssl_protocols TLSv1.3
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# https://hstspreload.org
add_header Strict-Transport-Security "max-age=63072000" always;
# By default, HSTS header is not added to subdomain requests. If you have subdomains and want
# HSTS to apply to all of them, you should add the includeSubDomains variable like this:
#add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
Per site configuration files (conf.d)
Since Alpine v3.5, we ship NGINX with a default.conf
within the /etc/nginx/conf.d directory.
To add support for another website, you can add files with the .conf extension to this directory:
Contents of /etc/nginx/conf.d/alpinelinux.org.conf
server {
listen 80;
server_name alpinelinux.org;
location / {
include conf.d/proxy_set_header.inc;
proxy_pass http://downstream_http_server_host;
}
}
Common configuration includes
If you need to setup multiple proxy setups, you can include duplicated data such as shown below:
Contents of /etc/nginx/conf.d/proxy_set_header.inc
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
acme-client
To allow NGINX to support https, we need to add certificates and support for ACME (Automatic Certificate Management Environment) responses.
ACME responses
Contents of /etc/nginx/conf.d/acme.inc
# Allow access to the ACME Challenge for Let's Encrypt
location ^~ /.well-known/acme-challenge {
allow all;
alias /var/www/acme;
}
And add this to your proxy configuration:
Contents of /etc/nginx/conf.d/alpinelinux.org.conf
server {
listen 80;
server_name alpinelinux.org;
include conf.d/acme.inc;
location / {
include conf.d/proxy_set_header.inc;
proxy_pass http://downstream_http_server_host;
}
}
Automatic generation of certificates
Create the following file:
Contents of /etc/periodic/weekly/acme-client
#!/bin/sh
hosts="alpinelinux.org"
for host in $hosts; do
acme-client -a https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf -Nnmv $host && renew=1
done
[ "$renew" = 1 ] && rc-service nginx reload
Make it executable:
chmod +x /etc/periodic/weekly/acme-client
This script will run weekly to verify whether one of your certificates is outdated and renew them when needed.
If you have several domains, you can add them to the hosts= variable with a space between each domain. This will create a separate certificate and key for each:
hosts="alpinelinux.org example.com foo.org bar.io"
Initial generation of keys and certificates
To create your initial certificates and keys, you have to run this manually the first time:
/etc/periodic/weekly/acme-client
Watch the output and see if all goes well. When it's finished, you should have files in:
/etc/ssl/acme/alpinelinux.nl/fullchain.pem
/etc/ssl/acme/private/alpinelinux.org/privkey.pem
NGINX HTTPS
Per site HTTPS configuration
Add the following below the previous HTTP configuration:
Contents of /etc/nginx/conf.d/alpinelinux.org.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name alpinelinux.org
ssl on;
ssl_certificate /etc/ssl/acme/alpinelinux.org/fullchain.pem;
ssl_certificate_key /etc/ssl/acme/private/alpinelinux.org/privkey.pem;
include /etc/nginx/conf.d/ssl-params.inc; # SSL parameters
location / {
include conf.d/proxy_set_header.inc;
proxy_pass http://downstream_http_server_host;
}
}
Redirect HTTP to HTTPS
Shared configuration
Create the following file:
Contents of /etc/nginx/conf.d/redirect_http.inc
location / {
return 301 https://$host$request_uri;
}
Update host configuration
Contents of /etc/nginx/conf.d/alpinelinux.org.conf
server {
listen 80;
server_name alpinelinux.org;
include conf.d/acme.inc;
include conf.d/redirect_http.inc;
}
Complete host example with IPv6 support
Contents of /etc/nginx/conf.d/alpinelinux.org.conf
server {
listen 80;
listen [::]:80;
server_name alpinelinux.org;
include conf.d/acme.inc;
include conf.d/redirect_http.inc;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name alpinelinux.org;
ssl on;
ssl_certificate /etc/ssl/acme/alpinelinux.org/fullchain.pem;
ssl_certificate_key /etc/ssl/acme/private/alpinelinux.org/privkey.pem;
include /etc/nginx/conf.d/ssl-params.inc; # SSL parameters
location / {
include conf.d/proxy_set_header.inc;
proxy_pass http://downstream_http_server_host;
}
}