# Debian Server Setup ## Contents - [Server Installation](#server-installation) - [Server User Setup](#server-user-setup) - [Disable root Login](#disable-root-login) - [Server Hardening](#server-hardening) - [fail2ban Setup](#fail2ban-setup) - [Apache Webserver](#apache-webserver) - [Apache iptables Ports](#apache-iptables-ports) - [Apache Default Template](#apache-default-template) - [Apache 80 Template](#apache-80-template) - [Apache 443 Template](#apache-443-template) ## Server Installation Install **Debian 10** using the Minimal setup method, add the SSH server option during the final steps on the installation. This is the default image delivered from many cloud providers; it may use the default hostname `localhost` - if desired, set a new one: ``` hostnamectl set-hostname myhostname ``` Ensure the hostname resolves locally - it does not have to be `127.0.0.1` (localhost) nor a FQDN, for example this works: ``` 127.0.0.1 localhost 127.0.1.1 myhostname ``` Adjust as needed based on how `/etc/hosts` is already configured from the installation. ## Server User Setup > **Use a very secure password** - at a minimum use `pwgen -sB 15`, strong password security encouraged! Set up a non-root user and add to the `sudo` group, then add a password. Use this user for SSH access and become root once logged in with sudo; if you have used SSH keys to log in as root, copy to this new user's setup as well if needed: ``` apt-get update apt-get install sudo export MYUSER="frankthetank" useradd -m -d /home/${MYUSER} -s /bin/bash -g users -G sudo ${MYUSER} passwd ${MYUSER} ``` If you are unable to use `ssh-copy-id` from your workstation to add a new SSH key, perform the work manually: ``` mkdir /home/${MYUSER}/.ssh cp /root/.ssh/authorized_keys /home/${MYUSER}/.ssh/ chmod 0700 /home/${MYUSER}/.ssh chmod 0600 /home/${MYUSER}/.ssh/authorized_keys chown -R ${MYUSER}:users /home/${MYUSER}/.ssh ``` > **SSH in as this user and test `sudo` several times** - log out completely between tests ### Disable root Login **If the above is successful** and you are capable of gaining full root privileges via the non-root SSH session using sudo, now disable root logins in SSH from the outside world for an additional security layer. The `root` account still remains usable, just not via _direct_ SSH access. The task is to set `PermitRootLogin no` - the setting varies from one provider to another, sometimes it's already set (either yes or no), sometimes it's commented out. This small scriptlet should handle these 2 most common cases, **be careful** and investigate for yourself: ``` _SCFG="/etc/ssh/sshd_config" if $(grep -iEq '^PermitRootLogin[[:space:]]+yes' "${_SCFG}"); then sed -i.bak -e 's/^PermitRootLogin.*/PermitRootLogin no/gi' "${_SCFG}" else sed -i.bak -e 's/^#PermitRootLogin.*/PermitRootLogin no/gi' "${_SCFG}" fi ``` **After confirming the change is correct**, restart the SSH core daemon (it will not log you out): ``` systemctl restart sshd ``` **Test logging in again** to ensure the changes are as expected. Do not log out of the active, working SSH session as root until you've confirmed in _another_ session you can log in as your non-root user and still gain `sudo` to root. ## Server Hardening **1.** The Debian default vimrc (`set mouse=a`, `/usr/share/vim/vim80/defaults.vim`) messes up middle-mouse click paste when remote via SSH, override the setting to just disable the mouse: ``` echo 'set mouse=' >> ~/.vimrc ``` **2.** Install a few basic packages to make life a little nicer; typically the minimal install / cloud instances are stripped down and need a few things added, both for security and ease of use. Adjust as desired: ``` apt-get update echo "iptables-persistent iptables-persistent/autosave_v4 boolean true" | debconf-set-selections echo "iptables-persistent iptables-persistent/autosave_v6 boolean true" | debconf-set-selections echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections apt-get install sysstat unattended-upgrades iptables-persistent man less vim rsync bc net-tools git strace ``` The `smem` package will pull in a lot of X dependencies due to an embedded recommendation, install it while disabling that feature. This utility can be used to quickly query memory usage (including swap) on the memory constrained cloud server: ``` apt-get install smem --no-install-recommends ``` **3.** Enable `journald` to store logs on disk instead of just RAM. By default, the `journald` system is in automatic mode - on boot it will create the ephemeral tmpfs `/run` out of RAM, but will _only_ transition to storing the journal on disk (out of RAM) if this directory exists. ``` mkdir /var/log/journal ``` **4.** Enable _sysstat_ for ongoing statistics capture of your instance (use `sar` to view): ``` sed -i.bak -e 's|^ENABLED=".*"|ENABLED="true"|g' /etc/default/sysstat ``` **5.** Enable _unattended-upgrades_ to ensure that all Security updates are applied: ``` cat << 'EOF' > /etc/apt/apt.conf.d/02periodic APT::Periodic::Enable "1"; APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "5"; APT::Periodic::Unattended-Upgrade "1"; EOF ``` **6.** Enable the basic _iptables_ rules to allow only port 22: ``` cat << 'EOF' > /etc/iptables/rules.v4 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT EOF cat << 'EOF' > /etc/iptables/rules.v6 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT -A INPUT -p ipv6-icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp6-adm-prohibited -A FORWARD -j REJECT --reject-with icmp6-adm-prohibited COMMIT EOF ``` **7.** Add a bit of swap if needed - using swap is not bad in and of itself, the Linux kernel will attempt to cache it's small bits of data if available. A cloud instance may not be delivered with any swap configured. ``` # 128M swap file dd if=/dev/zero of=/swap.file bs=1024 count=128000 chmod 0600 /swap.file mkswap /swap.file echo '/swap.file none swap defaults 0 0' >> /etc/fstab swapon /swap.file ``` **8.** Finally, ensure all the services are enabled and apply all outstanding updates; reboot as needed for a new kernel. If you don't reboot here, you'll need to `service` _foo_ `restart` each one individually (just reboot, it's easier): ``` systemctl disable remote-fs.target systemctl enable sysstat unattended-upgrades netfilter-persistent apt-get dist-upgrade -y reboot ``` ### fail2ban Setup Recommended: configure fail2ban to keep an eye on the SSH port for brute force attacks. > **Note**: `fail2ban` tends to consume a fair amount of memory the longer it runs; if the cloud server is memory constrained, you may wish to skip this step or disable the service later. Use `smem` to monitor it periodically. ``` apt-get install fail2ban sqlite3 cat << 'EOF' > /etc/fail2ban/jail.local [DEFAULT] ignoreip = 127.0.0.1/8 bantime = 600 maxretry = 3 backend = auto destemail = root@localhost EOF systemctl enable --now fail2ban ``` Additionally, add a weekly `cron` task to purge the database of old IPs (bug in 0.9.x series) and to restart the daemon to free up it's RAM usage: ``` cat << 'EOF' > /etc/fail2ban/dbpurge.sql delete from bans where timeofban <= strftime('%s', date('now', '-7 days')); vacuum; .quit EOF cat << 'EOF' > /etc/cron.weekly/f2b-cleanup #!/bin/sh if [ -x /usr/bin/sqlite3 ]; then sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 < /etc/fail2ban/dbpurge.sql fi systemctl restart fail2ban.service EOF chown root:root /etc/cron.weekly/f2b-cleanup chmod 0755 /etc/cron.weekly/f2b-cleanup ``` ## Apache Webserver Optional: adding a webserver might be desired, the method of obtain the SSL certificate is not covered here. ### Apache Installation The Debian package includes the SSL libraries, a few extra modules need to be enabled to support the extra security tuning in the templates. ``` apt-get update apt-get install apache2 a2enmod ssl a2enmod reqtimeout a2enmod rewrite a2enmod headers a2enmod expires ``` ### Apache iptables Ports Ensure the ports for 80 and 443 are added to `/etc/iptables/rules.v4` and `/etc/iptables/rules.v6`, typically near where the SSH port has been opened: ``` -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT ``` Restart the daemon: `systemctl restart netfilter-persistent` ### Apache Default Template This is the main template setting up parameters for all virtualhosts; the choice to include the virtual hosts in this template is not required, only a stylistic choice of the author. Save this to `/etc/apache2/sites-available/00_main.conf` (or use a symlink): ``` Timeout 60 KeepAlive Off MaxKeepAliveRequests 100 KeepAliveTimeout 15 ServerName localhost ServerTokens OS TraceEnable off StartServers 3 MinSpareServers 2 MaxSpareServers 4 ServerLimit 9 MaxClients 9 MaxRequestsPerChild 2000 RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500 AllowOverride None Require all granted # Port 80 Include /path/to/port_80.conf # Port 443 Include /path/to/port_443.conf ``` Disable the Debian default website and enable the new one created above: ``` a2dissite 000-default a2ensite 00_main ``` ...or just manually change symlinks in `/etc/apache2/sites-enabled/` as desired. ### Apache 80 Template Included above as `/path/to/port_80.conf` ``` ServerName example.com ServerAlias www.example.com ServerAdmin root@example.com ErrorLog /var/log/apache2/example-error.log CustomLog /var/log/apache2/example-access.log combined DocumentRoot /path/to/www/html Options FollowSymLinks AllowOverride All Require all granted ``` ### Apache 443 Template Included above as `/path/to/port_443.conf` ``` ServerName example.com ServerAlias www.example.com ServerAdmin root@example.com ErrorLog /var/log/apache2/example-error.log CustomLog /var/log/apache2/example-access.log combined SSLEngine on SSLHonorCipherOrder on SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLHonorCipherOrder on SSLCompression off SSLSessionTickets off SSLCertificateFile /path/to/sslkeys/2020-example.crt SSLCertificateKeyFile /path/to/sslkeys/2020-example.key SSLCACertificateFile /path/to/sslkeys/2020-ssl-issuer-CA.pem Header always set Strict-Transport-Security "max-age=15768000" SSLOptions +StdEnvVars SetEnvIf User-Agent ".*MSIE.*" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 DocumentRoot /path/to/www/html Options FollowSymLinks AllowOverride All Require all granted ``` Note the above 443 template does not enable HSTS on all subdomains by design, add as required.