Webserver configuration

This is a document detailing our current webserver setup. It is intended to help anyone wishing to improve or maintain our online infrastructure.

  • The webserver is based on Debian
  • NFTables for our firewall
  • Crowdsec for IP banning
  • Caddy for our frontend web proxy
  • SystemD and linux groups to manage our web services
  • PostgreSQL as our database
  • Disable password authentication
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority filter;

                # Allow loopback (local connections)
                iifname lo accept

                # Allow established/related
                ct state established,related accept

                # Allow incoming pings
                ip protocol icmp limit rate 2/second accept
                meta l4proto ipv6-icmp limit rate 2/second accept

                # Allow SSH and HTTP(s)
                tcp dport { ssh,http,https } accept

                # Drop everything else
                drop


        }
        chain forward {
                type filter hook forward priority filter;

                # Disallow forwarding
                drop
        }
        chain output {
                type filter hook output priority filter;

                # Allow all outgoing traffic
                accept
        }
}

Generally follow the Crowdsec install instructions https://docs.crowdsec.net/docs/getting_started/install_crowdsec/.

Install the NFTables firewall bouncer sudo apt install crowdsec-firewall-bouncer-nftables

Install following the standard Debian instructions https://caddyserver.com/docs/install#debian-ubuntu-raspbian.

Configure PostgreSQL to listen on a unix socket:

postgresql.conf

port = 5432
unix_socket_directories = '/var/run/postgresql'
unix_socket_group = 'pguser'
unix_socket_permissions = 0770

Check that local connections are using peer authentication:

pg_hba.conf

# "local" is for Unix domain socket connections only
local   all             all                                     peer
  1. Create a user for that service. For example the Wiki has a user called wiki
  2. Create a folder in /srv with the same user and group.
  3. Set the SGID bit so the group is inherited e.g. sudo chmod g+s /srv/wiki
  4. Set an ACL to set the default group permissions to rwx with the following sudo setfacl -Rdm g::rwx /srv/wiki

Once created, the service can be cloned or copied into this folder.

There are two files required, a .socket and a .service file. These will be placed in /etc/systemd/system/

Example /etc/systemd/system/website.socket

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/website.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation.
# Only Caddy will need access to the socket:
SocketUser=caddy
SocketGroup=caddy
# Once the user/group is correct, restrict the permissions:
SocketMode=0660

[Install]
WantedBy=sockets.target

Example /etc/systemd/system/website.service

[Unit]
Description=website daemon
Requires=website.socket
After=network.target

[Service]
# gunicorn can let systemd know when it is ready
Type=notify
NotifyAccess=main
# the specific user that our service will run as
User=website
Group=website
RuntimeDirectory=website
WorkingDirectory=/srv/website
ExecStart=/srv/website/.venv/bin/gunicorn -w 2 'hackspace_website:create_app()'
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
# if your app does not need administrative capabilities, let systemd know
# ProtectSystem=strict

[Install]
WantedBy=multi-user.target
; Start a new pool named 'wiki'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('wiki' here)
[wiki]

; Unix user/group of the child processes.
user = wiki
group = wiki

; The address on which to accept FastCGI requests.
listen = /run/php/wiki.sock

; Set permissions for unix socket, if one is used.
listen.owner = caddy
listen.group = caddy
;listen.mode = 0660

; Choose how the process manager will control the number of child processes.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
pm.max_children = 5

; The number of child processes created on startup.
pm.start_servers = 2

; The desired minimum number of idle server processes.
pm.min_spare_servers = 1

; The desired maximum number of idle server processes.
pm.max_spare_servers = 3

; The number of rate to spawn child processes at once.
;pm.max_spawn_rate = 32

; Redirect worker stdout and stderr into main error log.
catch_workers_output = yes

;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.wiki.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

Simple gunicorn based services can have a minimal reverse proxy:

bristolhackspace.org {
    log
    reverse_proxy unix//run/website.sock
}

PHP services require a little more setup:

mosparo.bristolhackspace.org {
  encode gzip zstd
  root * /srv/mosparo/public
  
  route {
    try_files {path} /index.php
  }
  
  php_fastcgi unix//var/run/php/mosparo.sock
  file_server
}

Some services such as the Wiki will require more complicated rewrite rules (generally services originally designed to run under Apache)

Some services will require a connection to the postgresql database. This is done via a unix socket with a set group. To give a service access:

  1. Add the service user to the pguser group.
  2. For python/sqlalchemy services use a connection string like so postgresql+psycopg2://website@/website replacing website with your service name.
  3. For other services you may need to set the database host to /var/run/postgresql and port to 5432.
  • resources/webserver
  • Last modified: 3 weeks ago
  • by samp20