yoursunny.com is Served by Caddy
Tags: linux, php, hosting, caddy
Published: 2021-01-13 05:20:39

> This is a personal story on how I'm keeping a 15-year-old website alive.
> Read along if you want to see a comparison among the various HTTP servers and TLS certificate solutions I used during the past 10 years.
> This post is originally published on yoursunny.com blog https://yoursunny.com/t/2021/yoursunny-com-caddy/

The last rebuild of **yoursunny.com** was in Spring 2017, when I [moved the whole website into git repositories](https://yoursunny.com/t/2017/yoursunny-com-git/).
It's been more than 3 years, and I think I should share an update on a few changes in the stack that serves this website.

## History of HTTP Servers Behind yoursunny.com

Since 2011, my HTTP server of choice was [lighttpd](https://www.lighttpd.net/).
Then, I have PHP running in FastCGI mode to serve the dynamic pages.
It works, but I don't really like the lighttpd's script-like configuration structure.
Moreover, there were suspected memory leaks in my setup, so that I had to use a cron job to restart the HTTP server weekly.

I keep hearing good words about [nginx](https://nginx.org/en/docs/), as well as the benefits of running PHP in [FPM mode](https://www.php.net/manual/en/install.fpm.php).
In 2013, I [made the switch](https://twitter.com/yoursunny/status/409242213559070720) to nginx and PHP-FPM.
The declarative configuration of nginx is easy to understand and makes sense to me.

HTTPS came to yoursunny.com [at the end of 2015](https://twitter.com/yoursunny/status/682632871740354560).
Like many other websites, the TLS certificates were issued by [Let's Encrypt](https://letsencrypt.org/), and requested through [certbot](https://certbot.eff.org/) command line client.
One problem is that, my VPS at the time had only 64MB memory, and certbot would not work in such a small amount of memory.
I had to request the TLS certificate on my laptop, and then upload it to the VPS.

I bought a larger VPS with 1GB memory in 2016.
With ample memory, certbot was able automatically obtain the certificate every other month.
However, I soon found that although certbot wrote the new certificates to the filesystem, nginx would continue to use the old certificates until it is restarted.
Thus, every time I receive a *Certificate Transparency Notification* email, I had to SSH into the server and restart nginx.
I'm sure there are ways to automate this, but I never tried to figure out.

In November 2020, I had a brief interaction with [acme.sh](https://acme.sh), an ACME protocol client written in Unix Shell language.
It's easy to understand, consumes minimal resources, and can be configured to restart nginx very easily.
acme.sh quickly replaced certbot in all my setups, until I found an easier way.

## Caddy HTTPS Server

nginx is fast, and acme.sh handles all my certificate needs.
I followed the advice on [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/) and received an "A" rating on [SSL Server Test](https://www.ssllabs.com/ssltest/index.html).
Things have been working well so far.

A few years later, I noticed that my green "A" rating turned into a yellow "B" rating, with a warning message:

> This server supports TLS 1.0 and TLS 1.1. Grade capped to B.

What happened?

It turns out that, HTTPS is more than a padlock: as the Internet evolves, the security recommendations are also evolving.
An nginx configuration written in 2016, adapted to the browsers and mobile devices at that time, is no longer secure in 2020, because over the years, protocol vulnerabilities are discovered and attacks are becoming more sophisticated.
If I want to keep the "A" rating, I have to keep the nginx configuration up to date with the current security recommendations.

Then I discovered an easier method: there's a new HTTP server called **Caddy**.
[Caddy](https://caddyserver.com/) is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.
Caddy automatically obtains and renews TLS certificates, uses a hardened TLS stack powered by the Go standard library, and has a secure-by-default TLS configuration complete with OCSP stapling.

Instead of:

* nginx
* acme.sh or certbot
* ~~periodically restart nginx~~
* annual nginx configuration check-up

I just need:

* Caddy 2

## Caddy + nginx + PHP-FPM

If I want to run a simple HTTPS server serving static files, I just need:

* Caddy 2

In reality, yoursunny.com is a 15-year-old website with hundreds of pages.
Apart from HTTPS, I also need:

* PHP script to render Markdown into HTML
* PHP script to retrieve my Twitter updates
* PHP script to display a list of blog articles on the frontpage
* redirect rules to keep the query strings working in `/study` subsite
* redirect rules to keep old links under `/work/` directory working

All these have been written in the nginx configuration.
Caddy is powerful and robust, but I'm not ready to rewrite all these into a Caddyfile.
Moreover, a critical feature is missing in Caddy: there isn't a [caching layer](https://github.com/caddyserver/cache-handler/issues/1).

How I designed my PHP scripts is that, they depend on the HTTP server to support caching.
For example, I have a [PHP script](https://bitbucket.org/yoursunny/yoursunny-website/src/670e313feccc25cc3f54d39cd4ed43bb9a5df7be/www/m/feed.php) that retrieves my [recent tweets](https://twitter.com/yoursunny) and then render them into an HTML snippet.
Every execution of this script involves a network request and a Twig template execution, which would consume quite some server resources.
If I was working with a shared hosting account, the script would have a temporary file mechanism such that it only performs the expensive operations every few minutes.
However, since I have been able to control the HTTP server, I shifted the caching responsibility to nginx, so that the PHP script itself would be simpler.

To solve these problems, I decided to keep using nginx for the website logic.
Therefore, the current setup is:

* Caddy:

  * Redirect plain HTTP (port 80) to HTTPS (port 443).
  * Terminate TLS.
  * Accept HTTP/1 and HTTP/2 requests on port 443.
  * Forward all requests to nginx over a Unix socket.
  * HTTP/3 is disabled at the moment, because my current hosting provider has a DDoS protection firewall that rate-limits IPv4 UDP traffic.

* nginx:

  * Process all HTTP/1.1 requests.
  * Handle redirect rules.
  * Forward PHP requests to PHP-FPM over another Unix socket.
  * Cache FastCGI responses from PHP scripts.
  * Static files are handled by nginx rather than Caddy, because some of my redirect rules are written as 404 handlers.

* PHP-FPM:

  * Execute all my scripts.

## Security Headers

Having Caddy handle TLS termination, yoursunny.com is back to the green "A" rating on [SSL Server Test](https://www.ssllabs.com/ssltest/index.html).
Then I found a new website security rating system: [Security Headers](https://securityheaders.com/).
It verifies and makes recommendations on a few security-related HTTP response headers from my server.

Starting from a big red "F", I have been reading the guidelines and making changes to the Caddyfile as I begin to understand what they are about.
As of this writing, I'm receiving a yellow "B" rating on [Security Headers](https://securityheaders.com/):

* ✔️ Referrer-Policy
* ✔️ Strict-Transport-Security
* ✔️ X-Content-Type-Options
* ✔️ X-Frame-Options
* ❌ Content-Security-Policy
* ❌ Permissions-Policy

Not bad, if you consider that Google has a "D" and Cloudflare has a "C".

## Conclusion

This article goes behind the scenes of my website **yoursunny.com**.
It describes my evolving choice of HTTP servers and TLS solutions, and the current setup that combines Caddy and nginx.
Caddy and nginx configuration files behind this website, along with all other source code, are published in the [yoursunny-website git repository](https://bitbucket.org/yoursunny/yoursunny-website).