self hosting a website on a raspberry pi

Introduction

Using a Raspberry Pi as self hosting webserver is quite straight forward. Its intriguing to do everything by myself. From writing the simple html sites to hosting them.

Raspberry Pi

I bought a Raspberry Pi 4 with 2 GB of RAM, the standard housing and the official USB-C power supply. As OS I installed the Raspberry Pi OS Lite version on a 32 GB micro SD card.

After the first boot WLAN access and the ssh server need to be set up. The standard password for the user "pi" is "raspberry".

raspberry pi

IP addresses and domain

The raspberry pi is connected to my WLAN router. The router itself connects via LAN to my Kabeldeutschland (now Vodafone) router. Here port forwarding for port 80 and port 443 to the IP address of the raspberry pi needs to be configured.

port forwarding
port forwarding at the kabel router

Its useful if this internal IP address is not changing. So I configured my DHCP server on my pi-hole to do so.

dhcp settings
fix DHCP settings for webserver on the pi-hole config page

My ISP (Internet Service Provider) provides me with a fixed IPv4 address. This fixed IP is then connected to a domain, so that http://domain.de and http://www.domain.de are both directing to this destination. This is to be configured where you are managing the domain.

If you are not so fortunate its still possible to use a service like dynv6 to dynamically update the IP address every time it changes.

certbot for letsencrypt ssl certificates

For SSL encryption on the raspi you can use the certbot command line tool. I got the howto from here. Since I want to serve a plain domain as well as a www subdomain, I need two certificates. A simply domain rewrite would lead to a browser security exception.

apt install snapd
sudo snap install core; sudo snap refresh coresnapd
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo systemctl stop lighttpd
sudo certbot certonly --standalone -d domain.de
sudo certbot certonly --standalone -d www.domain.de

After installation it's useful to use this pre and post hooks for the automatic renewal of the certificates. They are valid for three months only. The certbot uses cron a job to keep the certificates valid.

/etc/letsencrypt/renewal-hooks/pre/stop-webserver.sh

#!/bin/bash
systemctl stop lighttpd

/etc/letsencrypt/renewal-hooks/post/start-webserver.sh

#!/bin/bash
cp /etc/letsencrypt/live/domain.de/privkey.pem /etc/lighttpd/ssl/domain.de
cp /etc/letsencrypt/live/domain.de/fullchain.pem /etc/lighttpd/ssl/domain.de
cp /etc/letsencrypt/live/domain.de/cert.pem /etc/lighttpd/ssl/domain.de
cat /etc/lighttpd/ssl/domain.de/privkey.pem /etc/lighttpd/ssl/domain.de/fullchain.pem > /etc/lighttpd/ssl/domain.de/ssl.pem
chmod 0600  /etc/lighttpd/ssl/domain.de/*.pem
cp /etc/letsencrypt/live/www.domain.de/privkey.pem /etc/lighttpd/ssl/www.domain.de
cp /etc/letsencrypt/live/www.domain.de/fullchain.pem /etc/lighttpd/ssl/www.domain.de
cp /etc/letsencrypt/live/www.domain.de/cert.pem /etc/lighttpd/ssl/www.domain.de
cat /etc/lighttpd/ssl/www.domain.de/privkey.pem /etc/lighttpd/ssl/www.domain.de/fullchain.pem > /etc/lighttpd/ssl/www.domain.de/ssl.pem
chmod 0600  /etc/lighttpd/ssl/www.domain.de/*.pem
systemctl start lighttpd 

install lighttpd

The last thing would be to install the lighttpd webserver. Please note the rewriting of http to https and the two certificates for the main-domain and the sub-domain.

/etc/lighttpd/lighttpd.conf

server.modules = (
"mod_indexfile",
"mod_access",
"mod_accesslog",
"mod_alias",
"mod_redirect",
"mod_rewrite",
"mod_openssl",
)

server.document-root        = "/var/www/html"
server.upload-dirs          = ( "/var/cache/lighttpd/uploads" )
server.errorlog             = "/var/log/lighttpd/error.log"
server.pid-file             = "/var/run/lighttpd.pid"
server.username             = "www-data"
server.groupname            = "www-data"
server.port                 = 80
debug.log-request-handling = "enable" 

# strict parsing and normalization of URL for consistency and security
# https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_http-parseoptsDetails
# (might need to explicitly set "url-path-2f-decode" = "disable"
#  if a specific application is encoding URLs inside url-path)
server.http-parseopts = (
"header-strict"           => "enable",# default
"host-strict"             => "enable",# default
"host-normalize"          => "enable",# default
"url-normalize-unreserved"=> "enable",# recommended highly
"url-normalize-required"  => "enable",# recommended
"url-ctrls-reject"        => "enable",# recommended
"url-path-2f-decode"      => "enable",# recommended highly (unless breaks app)
#"url-path-2f-reject"      => "enable",
"url-path-dotseg-remove"  => "enable",# recommended highly (unless breaks app)
#"url-path-dotseg-reject"  => "enable",
#"url-query-20-plus"       => "enable",# consistency in query string
)

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

compress.cache-dir          = "/var/cache/lighttpd/compress/"
compress.filetype           = ( "application/javascript", "text/css", "text/html", "text/plain" )

# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.conf.pl"
include "/etc/lighttpd/conf-enabled/*.conf"

#server.compat-module-load   = "disable"
server.modules += (
        "mod_compress",
        "mod_dirlisting",
        "mod_staticfile",
)

$SERVER["socket"] == ":443" {
        ssl.engine = "enable"
        ssl.pemfile = "/etc/lighttpd/ssl/domain.de/ssl.pem"
        ssl.ca-file = "/etc/lighttpd/ssl/domain.de/cert.pem"
        server.name = "domain.de"
        server.document-root = "/var/www/https"
        server.errorlog = "/var/log/lighttpd/domain.de/serror.log"
        #if someone uses www before the domain, rewriting along doesn't work since the certificate is send before rewriting
        $HTTP["host"] == "domain.de" {
                ssl.pemfile = "/etc/lighttpd/ssl/domain.de/ssl.pem"
        }
        $HTTP["host"] == "www.domain.de" {
                ssl.pemfile = "/etc/lighttpd/ssl/www.domain.de/ssl.pem"
        }
}

$HTTP["scheme"] == "http" {
# capture vhost name with regex conditiona -> %0 in redirect pattern
# must be the most inner block to the redirect rule
$HTTP["host"] =~ ".*" {
        url.redirect = (".*" => "https://%0$0")
        }
}

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

Now only setting up all of the html files to /var/www/https is left.