Base Ubuntu 18.04 Server Linux Hardening

Prepare the the server

We will update and setup the default locale's on the server, and enable the auto security updates. I haven't seen any issues by enabling the auto security updates so far. In most cases you would want to review the updates for production servers, so you can see any conflicting packages or dependency issues. I have been running this setup for 6 months without issues.

sudo apt update && apt upgrade && apt autoremove
sudo dpkg-reconfigure tzdata && sudo locale-gen de_DE.UTF-8 && sudo dpkg-reconfigure locales && sudo dpkg-reconfigure -plow unattended-upgrades

Edit the hosts file

sudo nano /etc/hosts

Edit the hosts file with your fqdn details

xxx.xxx.xxx.xxx   myhost.domain.com myhost
# The following lines are desirable for IPv6 capable hosts
xxx.xxx.xxx.xxx   myhost.domain.com myhost
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

Secure the server

We will setup UFW and block incoming ICMP requests so the server can't be pinged. It just hardens the security a little.

Setup UFW

sudo ufw default deny incoming
sudo ufw allow 22 && sudo ufw allow 80 && sudo ufw allow 443
sudo ufw enable
sudo ufw status #should show what we just configured

Block ICMP requests

sudo nano /etc/ufw/before.rules

Change these lines:

# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j DROP  
-A ufw-before-input -p icmp --icmp-type source-quench -j DROP   
-A ufw-before-input -p icmp --icmp-type time-exceeded -j DROP  
-A ufw-before-input -p icmp --icmp-type parameter-problem -j DROP  
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP  

Stop spoofing attacks

In sysctl.conf we can use net.ipv4 set as 0 to secure the server.

Resource: https://gist.github.com/lokhman/cc716d2e2d373dd696b2d9264c0287a3

sudo nano /etc/sysctl.conf

Example config:

# Uncomment the next line to enable packet forwarding for IPv6
#  Enabling this option disables Stateless Address Autoconfiguration
#  based on Router Advertisements for this host
#net.ipv6.conf.all.forwarding=1


###################################################################
# Additional settings - these settings can improve the network
# security of the host and prevent against some network attacks
# including spoofing attacks and man in the middle attacks through
# redirection. Some network environments, however, require that these
# settings are disabled so review and enable them as needed.
#
# Do not accept ICMP redirects (prevent MITM attacks)
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# _or_
# Accept ICMP redirects only for gateways listed in our default
# gateway list (enabled by default)
net.ipv4.conf.all.secure_redirects = 0
#
# Do not send ICMP redirects (we are not a router)
net.ipv4.conf.all.send_redirects = 0
#
# Do not accept IP source route packets (we are not a router)
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
#
# Log Martian Packets
net.ipv4.conf.all.log_martians = 1
#

###################################################################
# Magic system request Key
# 0=disable, 1=enable all
# Debian kernels have this set to 0 (disable the key)
# See https://www.kernel.org/doc/Documentation/sysrq.txt
# for what other values do
#kernel.sysrq=1

###################################################################
# Protected links
#
# Protects against creating or following links under certain conditions
# Debian kernels have both set to 1 (restricted) 
# See https://www.kernel.org/doc/Documentation/sysctl/fs.txt
#fs.protected_hardlinks=0
#fs.protected_symlinks=0

Setup Fail2Ban

Fail2Ban can be used to stop hack attempts. It uses "jail" configurations to verify and block ip addresses.

sudo apt install fail2ban

The default Fail2Ban config files are fine for most hack activity. You can see jail activity by using fail2ban-client status and fail2ban-client status sshd to see blocked ssh attempts.

Setup nginx

During this step we will:

  • Remove the default nginx site
  • Create a new site for Firefly III
  • Redirect http to https
  • Setup Diffie-Hellman parameter for DHE ciphersuites, which hardens nginx's security. Diffie-Hellman forces a dependency on TLS to agree on a shared key and negotiate a secure session.
  • Use SSL Ciphers
sudo rm /etc/nginx/sites-enabled/default
sudo touch /etc/nginx/sites-available/myhost.domain.com.conf
sudo ln -s /etc/nginx/sites-available/myhost.domain.com.conf /etc/nginx/sites-sudo enabled/myhost.domain.com.conf
sudo openssl dhparam 2048 > /etc/nginx/dhparam.pem
sudo nano /etc/nginx/sites-enabled/myhost.domain.com.conf

Here is an example config

server {
        listen       80;
        server_name  myhost.domain.com;
        rewrite ^ https://$http_host$request_uri? permanent;    # force redirect http to https
        server_tokens off;
    }
server {
	listen 443 http2;
	listen [::]:443 http2;
        ssl on;
        ssl_certificate /etc/letsencrypt/live/myhost.domain.com/fullchain.pem;        # path to your fullchain.pem
        ssl_certificate_key /etc/letsencrypt/live/myhost.domain.com/privkey.pem;    # path to your privkey.pem
        server_name myhost.domain.com;
        ssl_session_timeout 5m;
        ssl_session_cache shared:SSL:5m;

        # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
        ssl_dhparam /etc/nginx/dhparam.pem;

        # secure settings (A+ at SSL Labs ssltest at time of writing)
        # see https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:HIGH:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS';
        ssl_prefer_server_ciphers on;

        proxy_set_header X-Forwarded-For $remote_addr;

	    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;        
	    server_tokens off;

    	root /opt/myhost.domain.com/public;

	# Add index.php to the list if you are using PHP
    	client_max_body_size 300M;
    	index index.html index.htm index.php;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
        location ~ \.php$ {
              try_files $uri =404;
              fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
              fastcgi_index index.php;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              include fastcgi_params;

        }

        index index.php index.htm index.html;

        location / {
          try_files $uri $uri/ /index.php?$query_string;
          autoindex on;
          sendfile off;
        }
    }

Restart nginx to apply the new config

sudo systemctl restart nginx

Setup logrotate

I added logrotate. There shouldn't be any harm using logrotate for logs.

sudo nano /etc/logrotate.d/myhost.domain.com

Example config:

/opt/myhost/storage/logs/*.log
{
    weekly
    missingok
    rotate 2
    compress
    notifempty
    sharedscripts
    maxage 60
}

That's it!