Objective


I want a webserver under Docker on my Synology NAS serving multiple websites (virtualhosts) that support HTTPS.

Prerequisites

  1. Docker is already installed in my Synology NAS and accessible from an admin account shell.
  2. I've set up my router's public port 80 to map to port 8001 on the Synology NAS, and port 443 to map to port 8002. Port 80 on the NAS is used by the Synology's DSM interface, so my web server needs to run on a different port.
  3. The domain name for the web site has a A record which is dynamically DNS'd to my router's public IP (or a CNAME to the dynamic DNS name).

The Plan

  1. To setup nginx as a docker container running on synology NAS
    • Configuration files will be kept on a shared folder (/volume1/Docker/nginx-cerbot/)
    • The web document root for each virtualhost will be kept on shared folder (/volume1/Web/{virtualhostname})
    • This is so I can make changes to the web content easily.
  2. To setup certbot as a docker container running on synology NAS.
    • CertBot will be used to request SSL Certificates for all my virtual hosts from the LetsEncrypt service. To do this it needs to make publicly available a "challenge" file on my HTTP server, which LetsEncrypt will verify before it will issue me a SSL certificate.
    • The CertBot container will regularly renew the SSL certificates automatically, install them, and restart nginx. This is needed because LetsEncrypt certificates have a short expiry date (3 months).

I plan to initially set up two sites, initially:


Set Up Shared Storage

  • Create the following folders in NAS:
    • /volume1/docker/nginx-certbot
      • configuration files for docker containers and nginx
    • /volume1/docker/nginx-certbot/conf.d
      • nginx virtual host configuration files
    • /volume1/docker/Web
      • virtual host document root
    • /volume1/docker/Web/home.abubakar.net
      • document root for virtualhost home.abubakar.net
    • /volume1/docker/Web/repo.shahada.abubakar.net
      • document root for virtualhost repo.shahada.abubakar.net
    • /volume1/docker/Web/certbot
      • document root for certbot validation

Set up Virtual Hosts

Initially, the virtual hosts we set up will only handle HTTP. We can't set up HTTPS support until we have a valid SSL certificate. The HTTP virtual host is enough to serve the "/.well-known/acme-challange" location needed for requesting SSL certs from LetsEncrypt.

The HTTP site will also redirect any other traffic that hits it to a HTTPS version of itself (which at this moment doesn't exist yet). This will be used later to force any non-HTTPS traffic to the HTTPS site.

Each virtual host will need a nginx configuration file, which we create below:

  • Create the file /volume1/docker/nginx-certbot/conf.d/home.abubakar.net.conf:

server {
listen 80;
listen [::]:80;
server_name home.abubakar.net;

location /.well-known/acme-challenge/ {
root /web/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}


server {
listen 80;
listen [::]:80;

location /.well-known/acme-challenge/ {
root /web/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}
Note: If you're repeating these steps to add a new virtual host, and already have your containers running, you can skip "Setup Containers" below and just restart nginx using:
docker restart nginx-certbot_web_1
nginx-certbot_web_1

Setup Containers

Create the file /volume1/docker/nginx-cerbot/docker-compose.yaml:
version: '3'
services:
web:
image: nginx:latest
ports:
- "8001:80"
- "8002:443"
volumes:
- "/volume1/Web:/web"
- "/volume1/docker/nginx-certbot/conf.d:/etc/nginx/conf.d"
- "/volume1/docker/nginx-certbot/letsencrypt:/etc/letsencrypt"
environment:
- NGINX_PORT=80
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
image: certbot/certbot
volumes:
- "/volume1/Web:/web"
- "/volume1/docker/nginx-certbot/letsencrypt:/etc/letsencrypt"
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
Build images:
cd /volume1/docker/nginx-certbot
sudo docker-compose up
Pulling web (nginx:latest)...
Trying to pull repository docker.io/library/nginx ...
latest: Pulling from docker.io/library/nginx
b8f262c62ec6: Already exists
e9218e8f93b1: Already exists
7acba7289aa3: Already exists
Digest: sha256:aeded0f2a861747f43a01cf1018cf9efe2bdd02afd57d2b11fcc7fcadc16ccd1
Status: Downloaded newer image for docker.io/nginx:latest
Pulling certbot (certbot/certbot:latest)...
Trying to pull repository docker.io/certbot/certbot ...
latest: Pulling from docker.io/certbot/certbot
9d48c3bd43c5: Already exists
c0ea575d71b9: Already exists
0f535eceebd5: Already exists
8a30f5893bea: Already exists
c1d30ace7b67: Already exists
1c95d67da89c: Already exists
016f6bac5faf: Already exists
bdf3824d50bf: Already exists
b4991d12bca5: Already exists
5edb9336f604: Already exists
Digest: sha256:512d36d96ae1fe4bcfbb8e3562822b3ad208a2750bbf3ba3eb746a0132982254
Status: Downloaded newer image for docker.io/certbot/certbot:latest
Starting nginxcertbot_certbot_1 ...
Starting nginxcertbot_web_1 ... done
Attaching to nginxcertbot_certbot_1, nginxcertbot_web_1
List containers to confirm it's running:
  • docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aede81ccb9ec nginx:latest "nginx -g 'daemon ..." About a minute ago Up 19 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp nginxcertbot_web_1
31d14c914336 certbot/certbot "/bin/sh -c 'trap ..." 7 minutes ago Up 19 seconds 80/tcp, 443/tcp nginxcertbot_certbot_1


Request SSL Certificates

Next we will request a SSL certificate for each of our virtual hosts. Replace "home.abubakar.net" below with the name of each virtual host.

First do a dry run to test, and if you have any results other than the below, investigate why:
docker exec -it nginx-certbot_certbot_1 certbot --dry-run certonly -d home.abubakar.net --agree-tos -m shahada@abubakar.net --webroot --webroot-path /web/certbot -n
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Cert not due for renewal, but simulating renewal for dry run
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for home.abubakar.net
Using the webroot path /web/certbot for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- The dry run was successful.
Then do the real thing (repeat the above command without the "--dry-run"):
docker exec -it nginx-certbot_certbot_1 certbot --dry-run certonly -d home.abubakar.net --agree-tos -m shahada@abubakar.net --webroot --webroot-path /web/certbot -n
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for home.abubakar.net
Using the webroot path /web/certbot for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/home.abubakar.net/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/home.abubakar.net/privkey.pem
Your cert will expire on 2020-06-17. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

Create virtual host for SSL Sites

Now that we have the SSL Certificates, we can create the HTTPS virtual host.

  • Create the file /volume1/Docker/nginx-certbot/conf.d/home.abubakar.net.ssl.conf:


server {
listen 443 ssl;
listen [::]:443 ssl;
server_name home.abubakar.net;

ssl_certificate /etc/letsencrypt/live/home.abubakar.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/home.abubakar.net/privkey.pem;

location / {
index index.html;
autoindex on;
}
}

  • Create the file /volume1/Docker/nginx-certbot/conf.d/repo.shahada.abubakar.net.ssl.conf:

# repo.shahada.abubakar.net (ssl)

server {
listen 443 ssl;
listen [::]:443 ssl;

ssl_certificate /etc/letsencrypt/live/repo.shahada.abubakar.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/repo.shahada.abubakar.net/privkey.pem;

location / {
index index.html;
autoindex on;
}
}
  • Retart nginx:
docker restart nginx-certbot_web_1
nginx-certbot_web_1
You should now be able to browse to the HTTPS version of your website!