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.
Overview
- 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
Base Debian setup
sshd config tweaks
- Disable password authentication
Firewall configuration
NFTables setup
#!/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 } }
Crowdsec setup
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
Caddy setup
Install following the standard Debian instructions https://caddyserver.com/docs/install#debian-ubuntu-raspbian.
Database setup
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
Adding new webservers
Setting up a directory
- Create a user for that service. For example the Wiki has a user called
wiki
- Create a folder in
/srv
with the same user and group. - Set the SGID bit so the group is inherited e.g.
sudo chmod g+s /srv/wiki
- Set an ACL to set the default group permissions to
rwx
with the followingsudo setfacl -Rdm g::rwx /srv/wiki
Once created, the service can be cloned or copied into this folder.
Setting up a SystemD socket (for python gunicorn based services)
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
Setting up phpfpm (for php based services)
; 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
Adding to Caddy
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)
Postgresql database connection
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:
- Add the service user to the
pguser
group. - For python/sqlalchemy services use a connection string like so
postgresql+psycopg2://website@/website
replacingwebsite
with your service name. - For other services you may need to set the database host to
/var/run/postgresql
and port to 5432.