If you find any error, please send me a quick heads-up.
Caddy is a modern web server with great defaults – so let’s use it for local development as well. This guide is for installing it on macOS.
Install Caddy
First install caddy via Homebrew:
brew install caddy
One thing you need to do once is to install after installation is trusting the root certificate.
Caddy has automatic TLS provisioning built-in – without configuration. That works for public domains (via Let’s Encrypt), but also for local and internal domains.
Caddy generates a root certificate and derives from it certificates for your local dev domains. But for your browser to not complain about these certificate, you must install the root certificate as trusted in your system once.
As the following call interacts with macOS internals, we need to call it with sudo
:
sudo caddy trust
Now the installation is finished.
Please note that this is one of the few times we interact with the caddy
executable directly. To start / stop / restart it later, we will use brew services
.
Starting & stopping
Caddy will be started / stopped / restarted using brew services:
brew services start caddy
brew services stop caddy
brew services restart caddy
While it is possible to reload the configuration changes, you can just restart it every time – the start up time is really fast, so don’t bother. 😄
Configuration
The configuration of caddy is at the config directory of your homebrew installation. That is either
/opt/homebrew/etc/Caddyfile
for M1 macs/usr/local/etc/Caddyfilee
for Intel macs
Edit this file in your favorite editor (there is a nice VS Code extension for syntax highlighting).
Example configs
Make yourself familiar with the options, the syntax and structure of a Caddyfile. What now follows are simple base configurations for different project types:
PHP project with single entry (like Symfony, etc…)
This config includes:
- connection PHP-FPM via unix socket
- a file server to serve static files
- TLS support
- The path to the document root
symfony.example.test {
tls internal
root * /var/www/symfony-project/public
file_server {
index index.php index.html
}
php_fastcgi unix//opt/homebrew/var/run/php8.1-fpm.sock
}
Please note the tls internal
:As of Nov 2022 Caddy doesn’t recognize .test
as a private TLD and doesn’t automatically use local TLS for it.
PHP project with a different entry point
This is basically the same config as above (and uses a different PHP version):
other-entry.example.test {
tls internal
root * /var/www/other-entry/public
file_server {
index app_dev.php
}
php_fastcgi unix//opt/homebrew/var/run/php7.4-fpm.sock {
index app_dev.php
}
}
Automatic projects by subdomain
One neat setup is having all .test
domain mapped locally (setup is described in the article about dnsmasq), putting all your project in a single directory and automatically mapping sub domains to your project directories.
In this example I assume every project is a project with a public/
directory as entry and with public/index.php
as single entry point:
*.projects.test {
tls internal
root * /var/projects/{http.request.host.labels.2}/public
file_server {
index index.php index.html
}
php_fastcgi unix//opt/homebrew/var/run/php8.1-fpm.sock
}
This setup is basically the same as above, except for two parts:
- The domain is
*.projects.test
: this automatically resolves all subdomains. - The segment in the path
{http.request.host.labels.2}
Caddy automatically parses the hostname for you and separates it into labels. These labels are numbered right to left, beginning with 0:
sub-sub-domain.sub-domain.example.test
╰─────┬──────╯ ╰───┬────╯ ╰──┬──╯ ╰┬─╯
3 2 1 0
Overwriting the automatic setup for specific domains
If you now have a project that needs different config (like PHP 8.0), you can just add a server block with a specific domain – it will have priority above the one with the wildcard:
*.projects.test {
# ...
}
old.projects.test {
tls internal
# you can keep using the placeholder, no need to hardcode "old" here
# but you can of course change the path here as well
root * /var/projects/{http.request.host.labels.2}/public
file_server {
index index.php index.html
}
php_fastcgi unix//opt/homebrew/var/run/php8.0-fpm.sock
}
That’s it!