High5!

Recently we wrote a post on Moving back to Lighttpd and Michael Dexter thought I could spend my time wisely and do a short write-up on our use of dehydrated with Lighttpd.

In order to start with dehydrated we of course need to install it:

# pkg install dehydrated

Once it's all installed you can find the dehydrated configration /usr/local/etc/dehydrated

Your hosts and domains you want to get certificates for need to be added to domains.txt. For example:

example.com www.example.com example1.com secure.example1.com

The first host/domain listed will be used as filename to store the keys and certificates. There are a number of examples in the file itself if you want to get funky.

Hooks

If you want to restart services or do anything special, for example in the case when new certificates are generated, there is a file called hooks.sh. This script allows you to hook into any part of the process and run commands during that part of the process.

The hook we are using is for deploy_cert(). We are going to use this hook for: – creating a PEM certificate for Lighttpd – change owner to www – restart Lighttpd

What that looks like is something like this:

deploy_cert() {
    cat "${KEYFILE}" "${CERTFILE}" > "${BASEDIR}/certs/${DOMAIN}/combined.pem"
    chown -R www "${KEYFILE}" "${FULLCHAINFILE}" "${BASEDIR}/certs/${DOMAIN}/combined.pem"
    service lighttpd restart
}

The last part that is needed is to make sure this is run every day with cron.

@daily  root /usr/local/bin/dehydrated -c

In most cases this will be all that is needed to get going with dehydrated.

Lighttpd

You will need to let Lighttpd know about dehydrated and point it to acme-challange in the .well-known directory. You can do this with an alias like:

alias.url += ("/.well-known/acme-challenge/" => "/usr/local/www/dehydrated/")

The Lighttpd config we are using for SSL/TLS is the following:

$SERVER["socket"] == ":443" {
  ssl.engine = "enable" 
  ssl.pemfile = "/usr/local/etc/dehydrated/certs/example.com/combined.pem"
  ssl.ca-file = "/usr/local/etc/dehydrated/certs/example.com/chain.pem"
  ssl.cipher-list = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-
CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384"
  ssl.dh-file = "/usr/local/etc/ssl/dhparam.pem" 
  ssl.ec-curve = "secp384r1"
  setenv.add-response-header = (
    "Strict-Transport-Security" => "max-age=31536000; includeSubdomains",
    "X-Frame-Options" => "SAMEORIGIN",
    "X-XSS-Protection" => "1; mode=block",
    "X-Content-Type-Options" => "nosniff",
    "Referrer-Policy" => "no-referrer",
    "Feature-Policy" =>  "geolocation none; midi none; notifications none; push none; sync-xhr none; microphone none; camera none; magnetometer none; gyroscope none; speaker none; vibrate none; fullscreen self; payment none; usb none;"  
  )
}

To finish it all you can now run dehydrated, in most cases would be:

# dehydrated -c

The complete Lighttpd config can be found in our Git Repository.

There are some FreeBSD machines in our infrastructure which run NGINX. After the recent announcement on the F5 purchase of NGINX we decided to move back to Lighttpd.

We have not seen a lot of open source projects doing well after the parent company got acquired. We used Lighttpd in the past, before the project stalled, doesn’t seem to be the case anymore. We decided to check it out again.

The configuration discussed here is roughly what we used NGINX for.

A lot of the options within Lighttpd are enabled by using modules. These are the modules we have enabled on all our Lighttpd servers.

server.modules = (
  "mod_auth",
  "mod_expire",
  "mod_compress",
  "mod_rewrite",
  "mod_redirect",
  "mod_alias",
  "mod_access",
  "mod_setenv",
  "mod_evhost",
  "mod_fastcgi",
  "mod_accesslog",
  "mod_openssl"
)

To specify which IP and port Lighttpd listens on is defined in a couple of different ways. For IPv4 server.port and server.bind are used. For IPv6 you have to use $SERVER[“socket”]. The same is true for the SSL config.

server.port = "80"
server.bind = "0.0.0.0"
$SERVER["socket"] == "[::]:80" { }
$SERVER["socket"] == "[::]:443" { }
$SERVER["socket"] == ":443" {
  ssl.engine = "enable"
  ssl.pemfile = "/usr/local/etc/ssl/certs/example.com/combined.pem"
  ssl.ca-file = "/usr/local/etc/ssl/certs/example.com/chain.pem"
  ssl.cipher-list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-
CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384"
  ssl.dh-file = "/usr/local/etc/ssl/certs/dhparam.pem"
  ssl.ec-curve = "secp384r1"
  setenv.add-response-header = (    "Strict-Transport-Security" => "max-age=31536000; includeSubdomains",
    "X-Frame-Options" => "SAMEORIGIN",
    "X-XSS-Protection" => "1; mode=block",
    "X-Content-Type-Options" => "nosniff",
    "Referrer-Policy" => "no-referrer",
    "Feature-Policy" =>  "geolocation none; midi none; notifications none; push none; sync-xhr none; microphone none; c
amera none; magnetometer none; gyroscope none; speaker none; vibrate none; fullscreen self; payment none; usb none;" 
  )
}

Lighttpd requires a PEM certificate. Which you can easily create with: # cat domain.key domain.crt > combined.pem

You can create the dhparam.pem file with: # openssl dhparam -out dhparam.pem 4096

These are the global settings we are using on FreeBSD related to the server settings.

server.username = "www"
server.groupname = "www"
server.pid-file = "/var/run/lighttpd.pid"
server.event-handler = "freebsd-kqueue"
server.stat-cache-engine = "disable"
server.max-write-idle = 720
server.tag = "unknown"
server.document-root = "/usr/local/www/default/"
server.error-handler-404 = "/404.html"
accesslog.filename = "/usr/local/www/logs/lighttpd.access.log"
server.errorlog = "/usr/local/www/logs/lighttpd.error.log"
server.dir-listing = "disable"

Some global settings which apply to all the websites served by Lighttpd.

index-file.names = ("index.php", "index.html", "index.htm")
url.access-deny = ("~", ".inc", ".sh", "sql", ".htaccess")
static-file.exclude-extensions = (".php", ".pl", ".fcgi")

Alias for Let's Encrypt.

alias.url += ("/.well-known/acme-challenge/" => "/usr/local/www/acme/")

Enable compression for certain filetypes.

compress.cache-dir = "/tmp/lighttpdcompress/"
compress.filetype = ("text/plain", "text/css", "text/xml", "text/javascript")

When authentication is needed you can specify this as below. Different backends are supported.

auth.backend = "htpasswd"
auth.backend.htpasswd.userfile = "/usr/local/etc/lighttpd/htpasswd"

General Expire and Cache-Control headers for certain filetypes.

$HTTP["url"] =~ "\.(js|css|png|jpg|jpeg|gif|ico)$" {
  expire.url = ( "" => "access plus 1 months" )
}

When you are running Wordpress sites you might want to deny access to certain urls.

$HTTP["url"] =~ "/(?:uploads|files|wp-content|wp-includes).*\.(php|phps|txt|md|exe)$" {
  url.access-deny = ("")
}
$HTTP["url"] =~ "/(wp-config|xmlrpc)\.php$" {
  url.access-deny = ("")
}

Define for which host and url the authentication is needed.

$HTTP["host"] =~ "www1.example.com" {
  auth.require = ( "/admin/" => (
    "method" => "basic",
    "realm" => "Restricted",
    "require" => "valid-user" )
  )
}

Redirect certain hosts from http to https.

$HTTP["host"] =~ "(www\.)?example.com" {
  url.redirect = ("^/(.*)" => "https://www.example.com/$1")
}

There is a module available which helps to assign the correct server.document-root for virtual hosts. This can be done with mod_evhost and we are using the following pattern:

$HTTP["host"] =~ "^(www.)?[^.]+\.[^.]+$" {
  evhost.path-pattern = "/usr/local/www/www.%2.%1/"
}

To be able to use pretty urls with Wordpress you can use the following mod_rewrite rules.

url.rewrite = (
  "^/(wp-.+).*/?" => "$0",
  "^/(.*)\.(.+)$" => "$0",
  "^/(.+)/?$" => "/index.php/$1"
)

The final piece of the puzzle for when you are using PHP-FPM the following config can be used.

fastcgi.server = ( ".php" =>
  ( "localhost" =>
    (
      "host" => "127.0.0.1",
      "port" => 9000
    )
  )
)

The complete config can be found in our Git Repository