I have some static (or with light CGI) sites I thought I'd like to host on my FreeBSD server. Apache HTTP Server seems to be one of the popular ways to do this on FreeBSD. I think I last used Apache back in the 1.x days, so I figured a lot had probably changed. Reading through the documentation, I was pleased to find that a lot of nice improvements had been made.
I'm using the pf
firewall, I added the http and https ports
to a rule in /etc/pf.conf
that allows incoming connections,
checked the configuration, and reloaded it.
pass in proto tcp to any port { ssh http https }
root@lucy:~ # pfctl -f /etc/pf.conf -n root@lucy:~ # service pf reload Reloading pf rules.
I started by installing the apache-24 package with
pkg install apache24
. I added an apache24_enable="YES"
line to /etc/rc.conf, but didn't start Apache yet. I wanted to set up a minimal
httpd.conf file first.
I decided to write a configuration file for Apache from scratch so I'd know what all was in it and why. I figured this would be a good exercise to get familiar with Apache again after so long away.
I moved /usr/local/etc/apache24/httpd.conf
to a backup file
and started fresh. First I configured the core module. FreeBSD can do some
magic in the kernel before passing a socket off to Apache. I didn't have the
kernel modules for this enabled, so I disabled the supporting options in
Apache so it wouldn't be confused. I planned to use name-based virtual hosts,
but I wanted to have a fallback site to show some (hopefully) helpful
information to anyone who arrived at my server via a host name that wasn't
configured as a virtual host. I made a new directory
/usr/local/www/default
to hold this default site's files.
The server name and admin contact information are used by Apache to build its
default error pages. I might add nicer pages for some or all of my sites later,
but in the meantime I didn't want to leave folks hanging without this
information.
# Core module AcceptFilter http none AcceptFilter https none DocumentRoot www/default ServerAdmin support@parksdigital.com ServerName vultr1.parksdigital.com
The authz_core module seems to be needed to handle an implicit
require all granted
directive on DocumentRoot
(or Location /
?). The dir module would let me serve index.html for
URLs that end in a directory. The MIME module sets the content-type header
automatically based on the extension of the served file. Apache now has a
choice of Multi-Processing Modules for handling the basic tasks of binding
network ports, accepting requests, and dispatching children to handle the
requests. The Event MPM seems to be popular and a good fit for FreeBSD.
Finally, I'd need the UNIXd module for changing to the www
user
after starting up (indeed, Apache will shut back down if it finds it's still
running as root after startup).
# SO module LoadModule authz_core_module libexec/apache24/mod_authz_core.so LoadModule dir_module libexec/apache24/mod_dir.so LoadModule mime_module libexec/apache24/mod_mime.so LoadModule mpm_event_module libexec/apache24/mod_mpm_event.so LoadModule unixd_module libexec/apache24/mod_unixd.so
I configured the event module to listen on port 80 of my server's public address.
# Event module Listen 69.63.227.51:80
And I configured the UNIXd module to change to the www user and group during startup.
# UNIXd module Group www User www
I started the server up with service apache24 start
.
The first VirtualHost section in the configuration will be used as the default if none of the others have a ServerName or ServerAlias directive which match the Host header of the request. I wanted to use the base server configuration as the fallback in this case, so I added an empty VirtualHost section for port 80 which would catch these requests and inherit the base server configuration.
<VirtualHost *:80> </VirtualHost>
I knew I'd have several virtual hosts, so I used the Macro module to simplify the configuration. Each virtual host would be served from a directory with the same name as the site. It's hard to guess how folks will type in the address of your site, so I wanted to catch both the bare domain name and the www host. My preference is to redirect www to the bare domain, but you could go the other way too. I used the Rewrite module for this task. There are other ways to do it, but I think I came up with a neat solution.
# SO module LoadModule macro_module libexec/apache24/mod_macro.so LoadModule rewrite_module libexec/apache24/mod_rewrite.so # Rewrite module RewriteOptions InheritDown RewriteCond %{HTTP_HOST} ^www\.(.+) [NC] RewriteRule (.*) %{REQUEST_SCHEME}://%1$1 [R=301] # Name virtual hosts <Macro VHost $name> <VirtualHost *:80> DocumentRoot www/$name ServerName $name ServerAlias www.$name RewriteEngine on </VirtualHost> </Macro> Use VHost parksdigital.com Use VHost gypsyroadstables.com Use VHost heartfx.net Use VHost neurostrategy.org Use VHost wirs.parksdigital.com
I made directories for each static site and set the ownership, then uploaded the site files.
for site in parksdigital.com gypsyroadstables.com heartfx.net \ neurostrategy.org wirs.parksdigital.com ; do mkdir /usr/local/www/$site chown user:www /usr/local/www/$site done
After checking the configuration file with httpd -t
, I reloaded
the configuration with service apache24 reload
.
A couple of my sites include some modest CGI scripts. To run them, I used Apache's CGId module. I also loaded the Env module for passing configuration to the scripts through environment variables. Since this configuration is sometimes secret, I changed the mode on httpd.conf so only the owner (root) can read and write it. The ScriptLog directive allows useful debugging information to be logged when something goes wrong with a script. Since this information might also contain sensitive information, I set its mode so that it can be read and written only by its owner (root) and read only by members of its group (wheel). Once everything was set, I tested the configuration and restarted Apache (usually I would just reload it, but in this case I think the restart is required to get the CGI daemon running).
# SO module LoadModule cgid_module libexec/apache24/mod_cgid.so LoadModule env_module libexec/apache24/mod_env.so # CGId module ScriptLog /var/log/httpd-script.log # CGI script directories <Directory /usr/local/www/*/cgi> Options +ExecCGI SetHandler cgi-script </Directory> <Directory /usr/local/www/parksdigital.com/cgi> SetEnv TWILIO_ACCOUNT_SID *** SetEnv TWILIO_AUTH_TOKEN *** </Directory> <Directory /usr/local/www/gypsyroadstables.com/cgi> SetEnv TWILIO_ACCOUNT_SID *** SetEnv TWILIO_AUTH_TOKEN *** </Directory>
chmod 600 /usr/local/etc/apache24/httpd.conf touch /var/log/httpd-script.log chmod 640 /var/log/httpd-script.log httpd -t service apache24 restart
I knew I'd want TLS available for my sites, so I also installed certbot with
pkg install py38-certbot
. I've been pretty happy in the past
with how certbot works, but I don't love that it brings in Python and a lot
of other dependencies. After looking at some of the other options, I decided
I liked it the best out of the bunch anyway.
I figured I'd use certbot's webroot authentication method. I thought it would be nice to have a single directory for certbot to use for all sites, so I wouldn't have to have a site set up in httpd.conf before requesting a certificate for the first time. Otherwise I'd have to set up the site in two stages: first without TLS to get the certificate then with TLS to use the certificate. I made a directory for certbot and used the Alias module in Apache to get what I was after.
mkdir -p /usr/local/www/certbot/.well-known/acme-challenge
# SO module LoadModule alias_module libexec/apache24/mod_alias.so # Alias module Alias /.well-known/acme-challenge www/certbot/.well-known/acme-challenge
I registered an account and requested certificates for each of my sites:
certbot register -m aparks@aftermath.net --agree-tos --no-eff-email certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name lucy.parksdigital.com \ -d lucy.parksdigital.com certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name parksdigital.com \ -d parksdigital.com -d www.parksdigital.com certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name gypsyroadstables.com \ -d gypsyroadstables.com -d www.gypsyroadstables.com certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name heartfx.net \ -d heartfx.net -d www.heartfx.net certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name neurostrategy.org \ -d neurostrategy.org -d www.neurostrategy.org certbot certonly --webroot -w /usr/local/www/certbot/ \ --cert-name wirs.parksdigital.com \ -d wirs.parksdigital.com
To get automatic certificate renewals, I added a
weekly_certbot_enable="YES"
line to /etc/periodic.conf.
It took some puzzling over the Apache and OpenSSL documentation along with some experimentation with SSL Labs test tool, but I came up with a configuration for Apache's SSL module which seems to get a good score while supporting a broad range of devices. I'm not completely certain that the SSLRandomSeed directives are necessary, but the documentation made it sound like Apache would use a questionable internal RNG if it's not given. I'd feel better if I had some guidance or basis for the number of bytes to read, too. The value I used seems to be common and, but I wasn't able to find the reasoning behind it. In my (poorly informed) judgment it's probably enough.
# SO module LoadModule ssl_module libexec/apache24/mod_ssl.so # Event module Listen 104.207.142.72:443 # SSL module SSLCipherSuite "ECDHE-ECDSA-AES128-GCM-SHA256:\ ECDHE-ECDSA-AES256-GCM-SHA384:\ ECDHE-ECDSA-AES128-SHA:\ ECDHE-ECDSA-AES256-SHA:\ ECDHE-ECDSA-AES128-SHA256:\ ECDHE-ECDSA-AES256-SHA384:\ ECDHE-RSA-AES128-GCM-SHA256:\ ECDHE-RSA-AES256-GCM-SHA384:\ ECDHE-RSA-AES128-SHA:\ ECDHE-RSA-AES256-SHA:\ ECDHE-RSA-AES128-SHA256:\ ECDHE-RSA-AES256-SHA384:\ DHE-RSA-AES128-GCM-SHA256:\ DHE-RSA-AES256-GCM-SHA384:\ DHE-RSA-AES128-SHA:\ DHE-RSA-AES256-SHA:\ DHE-RSA-AES128-SHA256:\ DHE-RSA-AES256-SHA256" SSLHonorCipherOrder on SSLProtocol all -TLSv1.1 -TLSv1 SSLRandomSeed startup file:/dev/urandom 512 SSLRandomSeed connect file:/dev/urandom 512
I added a default virtual host for TLS connections. This is a bit less useful than the default for regular connections since folks coming to a host whose name isn't set on one of the name virtual hosts will get a certificate warning, but I guess it's better to get the default site after the certificate warning rather than something unrelated.
# Core module Define cert_dir etc/letsencrypt/live # Default virtual host <VirtualHost *:443> SSLCertificateFile ${cert_dir}/vultr1.parksdigital.com/fullchain.pem SSLCertificateKeyFile ${cert_dir}/vultr1.parksdigital.com/privkey.pem SSLEngine on </VirtualHost>
Finally, I added a second VirtualHost directive to the VHost macro so that each site would have the option of TLS. At present, none of these sites require TLS but I like to provide the option for folks who would like it and so folks who are running HTTPS-everywhere don't get stuck (HTTPS-everywhere tries to use HTTPS if port 443 is listening; it doesn't have a way to check that the server is configured with a certificate for the particular name-virtual-host it's trying to connect to).
# Name virtual hosts <Macro VHost $name> ... <VirtualHost *:443> DocumentRoot www/$name ServerName $name ServerAlias www.$name RewriteEngine on SSLCertificateFile ${cert_dir}/$name/fullchain.pem SSLCertificateKeyFile ${cert_dir}/$name/privkey.pem SSLEngine on </VirtualHost>
FreeBSD has newsyslog
as part of the base system for log
rotation. It looks in /etc/newsyslog.conf.d
as well as
/usr/local/etc/newsyslog.conf.d
for application-specific
configuration files. I had to create the latter directory myself. In there
I made a file called apache24.conf
and added configuration to
rotate both the error and script logs daily and keep seven days worth of them.
I also set the configuration to send SIGUSR1 to Apache after rotating the
log files. This will cause it to do a graceful restart, re-opening the new
(empty) log files.
/var/log/httpd-error.log 640 7 * @T00 - /var/run/httpd.pid SIGUSR1 /var/log/httpd-script.log 640 7 * @T00 - /var/run/httpd.pid SIGUSR1
I hope that this has been helpful to you. If you found it interesting, you may also enjoy my other work. If you have any questions, corrections, or comments please drop me a line.
Aaron D. Parks