Now that my OpenBSD.Amsterdam VPS is up and running, and I have working backups, I thought I'd migrate some static sites over to this host and free up another dedicated server I'm using. Adding extra static HTML won't add to the VPS' general load and won't introduce new risks to #Chargen.One.

To do this, I need to implement name-based Virtual Hosting. I'm going to show how this is done for one site, hackingforfoodbanks.org, then build upon it for multiple hosts. Finally, I'll modularize elements of the configuration to make things more manageable, including HTTPS support.

To make Name-based virtual hosting work, it's necessary to update /etc/acme-client.conf, the DNS Records for the domain in question, and the nginx configuration.

Moving DNS

This is the simplest part of the job. It's simply a case of logging into a DNS provider, and pointing the relevant DNS records at the HTTP server. Log into the DNS provider or server, point the relevant 'A' and/or 'CNAME' records to the HTTP server's IP address, and be prepared to wait up to 24 hours.

Now DNS is out of the way, the next thing is to clean up the nginx config from earlier.

Segregating the Nginx config

The config as-is is fine for just hosting Chargen.One but could get a bit unwieldy if I move all of my static sites across. I created a subdirectory in /etc/nginx/ called sites, into which I can add server blocks for each site I want to host. This splits the configuration up into more manageable per-site blocks.

Before adding a new host, I split out the default chargen.one site config into a new file, /etc/nginx/sites/default.conf. This is a copy of the main /etc/nginx/nginx.conf site with everything from the openings server{ to closing } characters included. It looks like this:

server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /var/www/htdocs/c1; include acme.conf; #access_log logs/host.access.log main; #error_page 404 /404.html; # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/htdocs/c1; } # For reading content location ~ ^/(css|img|js|fonts)/ { root /var/www/htdocs/c1; # Optionally cache these files in the browser: # expires 12M; } location ~ ^/.well-known/(webfinger|nodeinfo|host-meta) { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:8080; proxy_redirect off; } location ~ ^/(css|img|js|fonts)/ { root /var/www/htdocs/c1; # Optionally cache these files in the browser: # expires 12M; } location /{ proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:8080; proxy_redirect off; } } # HTTPS server # server { listen 443 default_server; server_name _; root /var/www/htdocs/c1; include /etc/nginx/acme.conf; ssl on; ssl_certificate /etc/ssl/chargen.one.fullchain.pem; ssl_certificate_key /etc/ssl/private/chargen.one.key; ssl_session_timeout 5m; ssl_session_cache shared:SSL:1m; ssl_ciphers HIGH:!aNULL:!MD5:!RC4; ssl_prefer_server_ciphers on; location ~ ^/.well-known/(webfinger|nodeinfo|host-meta) { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:8080; proxy_redirect off; } location ~ ^/(css|img|js|fonts)/ { root /var/www/htdocs/c1; # Optionally cache these files in the browser: # expires 12M; } location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://127.0.0.1:8080; proxy_redirect off; } }

With that entire block removed from the main config, below the line server_tokens off; , there's just the following remaining in /etc/nginx/nginx.conf:

include /etc/nginx/sites/*.conf;

If I want to disable a site, I change the file extension from .conf to .dis and restart nginx. That way I can easily see which sites are enabled and which sites aren't without having to mess with the ln command or symbolic links.

Adding a new virtual host

The first host is the hardest, but onces up and running provides a template for any future hosts. I keep things fairly minimal, but adding support for PHP-based sites is as simple as copying from the default OpenBSD nginx config. The TLS config still points to the chargen.one certificate as only the certificate's associated hostnames change, not the filename.

server { listen 80; server_name hackingforfoodbanks.org www.hackingforfoodbanks.org; root /var/www/htdocs/hackingforfoodbanks; include /etc/nginx/acme.conf; #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/htdocs/hackingforfoodbanks; } location / { try_files $uri $uri/ =404; # Optionally cache these files in the browser: # expires 12M; } } # HTTPS server # server { listen 443; server_name hackingforfoodbanks.org www.hackingforfoodbanks.org; root /var/www/htdocs/hackingforfoodbanks; include /etc/nginx/acme.conf; ssl on; ssl_certificate /etc/ssl/chargen.one.fullchain.pem; ssl_certificate_key /etc/ssl/private/chargen.one.key; ssl_session_timeout 5m; ssl_session_cache shared:SSL:1m; ssl_ciphers HIGH:!aNULL:!MD5:!RC4; ssl_prefer_server_ciphers on; location / { root /var/www/htdocs/hackingforfoodbanks; # Optionally cache these files in the browser: # expires 12M; } }

The only major differences are the removal of default_server in the listen directives, the changes to server_name and root to point to the correct spot and the removal of all of the dynamic parts associated with Chargen.One. Check whether or not there are problems with the nginx config before restarting by using the following command:

nginx -t -c /etc/nginx/nginx.conf

Providing the syntax is ok, restart nginx with rcctl restart nginx as root, or via doas .

Adding domains to acme-client

The final part of the puzzle is to add LetsEncrypt support for the new domain. The easiest way to add domains to acme-client is through the alternative names feature. Here's what I've added to /etc/acme-client.conf in order to support the hackingforfoodbanks.org URL.

alternative names { hackingforfoodbanks.org www.hackingforfoodbanks.org }

After adding that, and deleting the existing /etc/ssl/chargen.one.crt file, acme-client can be called to add the new domain.

rm /etc/ssl/chargen.one.crt acme-client -vFAD chargen.one

Note that the alternative names for our new domains are under the chargen.one domain section. The domain section name is passed to acme-client , not the domain itself.

With a fully functioning certificate and nginx setup, run rcctl restart nginx to finish things off, and test the new site in a browser.

Adding HTTPS redirects

You might want to redirect some of your sites to HTTPS rather than serve a HTTP version of your site. While often touted as a panacea, this introduces a mix of advantages and drawbacks.

The content being delivered will be wrapped in transport layer encryption, making it harder for someone eavesdropping to identify the content being transferred (confidentiality)

As the transfer is encrypted, it becomes hard to interfere with the content.

HTTPS relies on a routinely (temporarily) broken permission-based model, often abused by companies and nation states. Thus while it's useful, it shouldn't be relied on for bulletproof 100% security.

Currently support TLS versions used in HTTPS aren't supported by most browsers available for legacy Operating Systems such as Windows XP. This means your site may be inaccessible over Windows XP and older versions of Android.

I'm not saying don't use HTTPS for a static site. There is no harm in supporting both, especially for a static web site. Just consider the site's audience and make a reasoned, deliberate decision as to whether or not to support accessing your content over HTTP before proceeding.

This site is accessible over HTTP and HTTPS precisely so users of older systems can still access the content via the reader, but authenticated access only works over HTTPS, and no mixed content is loaded.

As people accessing hackingforfoodbanks.org may not have access to current technology (e.g. foodbank users), I made a conscious decision to leave HTTP access open. For another site, rawhex.com, there's less of a requirement to leave HTTP access open, so I'll redirect that to HTTPS.

It's always annoying when a doc doesn't show the whole config for something complicated, so here's the /etc/nginx/sites/rawhex.conf file in full:

server { listen 80; server_name rawhex.com www.rawhex.com; return 301 https://$server_name$request_uri; } # HTTPS server # server { listen 443; server_name rawhex.com www.rawhex.com; root /var/www/htdocs/www.rawhex.com; include /etc/nginx/acme.conf; ssl on; ssl_certificate /etc/ssl/chargen.one.fullchain.pem; ssl_certificate_key /etc/ssl/private/chargen.one.key; ssl_session_timeout 5m; ssl_session_cache shared:SSL:1m; ssl_ciphers HIGH:!aNULL:!MD5:!RC4; ssl_prefer_server_ciphers on; # add HSTS header to ensure we don't hit the redirect again add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location / { root /var/www/htdocs/www.rawhex.com; # Optionally cache these files in the browser: # expires 12M; } }

The HTTP 301 redirect shrinks the rest of the block to almost nothing. The HSTS header is a way to ensure that once redirected, a browser will only make requests over HTTPS, even if the user clicks on a HTTP link. The end result is an A+ score from Qualys' SSL Labs. There are things that can be done to improve the score, but these come at the cost of compatibility with older browsers and Operating Systems such as Windows Vista and 7.

Modularizing further

You might've noticed in the above that I'm repeating a lot of SSL settings. For HTTPS sites, it's best to keep things consistent. As such I've moved my ssl settings (aside from HSTS) into a separate file, /etc/nginx/https.conf. This means I only have to change one file for all HTTPS site configs. The current version of my file looks like this:

ssl on; ssl_certificate /etc/ssl/chargen.one.fullchain.pem; ssl_certificate_key /etc/ssl/private/chargen.one.key; ssl_session_timeout 30m; ssl_session_cache shared:SSL:2m; ssl_ciphers HIGH:!aNULL:!MD5:!RC4; ssl_prefer_server_ciphers on;

I set a higher SSL Session timeout and cache for performance purposes. People should be able to use a single SSL session to cover a full visit to and around the site. People rarely spend longer than 30 minutes there unless they leave a tab open, at which point I'm happy to reinitialize.

Please don't confuse SSL Sessions with HTTP or application sessions. They're different things. If in doubt, the defaults are probably fine.

Now all I have to do is add include /etc/nginx/https.conf; below include /etc/nginx/acme.conf; to my sites, and any changes to ciphers or timeouts will be picked up systemwide with a single change.

Conclusion

Now that I can add static sites to the Chargen.One system, I'll migrate the rest of my content over. With a clean, modular nginx config, content is served speedily and thanks to OpenBSD, to a level of security I'm comfortable with. I still need to find somewhere to move my git repos to, and I'm not sure chargen.one is right for that, but roman has a few ideas that I might borrow from.