Skip to content

The Basic Guide To Secure Your Server

Ubuntu firewall open port Ubuntu firewall open port

Disclaimer

No system is safe. This chapter begins our exploration of Ubuntu server security. Remember however that skilled hackers or well-funded organisations can breach basic and even more advanced defences.

Firewall

A firewall regulates traffic to and from your server, safeguarding it against external threats. One widely-used firewall for Ubuntu servers is the Uncomplicated Firewall (ufw), renowned for its simplicity and effectiveness.

Step-by-step guide

Installation

Install the Uncomplicated Firewall, enable auto-start after each reboot, and verify it's running (the status should be Active):

sudo apt install ufw
sudo ufw enable
sudo systemctl enable ufw
sudo systemctl start ufw
sudo systemctl status ufw

Ubuntu firewall settings

Deny all incoming and outgoing traffic by default:

sudo ufw default deny outgoing
sudo ufw default deny incoming

Make sure the network firewall only authorises required traffic. This is done by opening the respective communication endpoints, also called ports. For the purpose of this tutorial, we'll only open a few of them. Adjust the commands according to your specific needs:

sudo ufw allow 80,443/tcp
sudo ufw allow 22/tcp
sudo ufw allow 123/udp
sudo ufw allow in from any to any port 53
sudo ufw allow out 80,443/tcp
sudo ufw allow out 22/tcp
sudo ufw allow out 123/udp
sudo ufw allow out from any to any port 53

Some additional information about those ports:

Port Description
80 HTTP requests: assigned to the commonly used internet communication protocol, Hypertext Transfer Protocol (HTTP). Your server might use it to send data to and receive data from the Web.
443 HTTPS requests: standard port for all secured HTTP traffic (HTTPS). Your server might use it to send and receive encrypted Web traffic.
22 SSH requests: usually used to run the Secure Shell (SSH) protocol. Your server might use it for remote logins.
123 NTP synchronisation: allows to synchronise time between computers.
53 DNS requests: used for domain name resolution. Your server might use it to turn human readable domain names into IP addresses.

Finally, display the Ubuntu firewall status and check if all rules are set correctly:

sudo ufw status numbered

Here's what the rules should look like:

Status: active

        To                  Action          From
        --                  ------          ----
[ 1]    80,443/tcp          ALLOW IN        Anywhere
[ 2]    22/tcp              ALLOW IN        Anywhere
[ 3]    123/udp             ALLOW IN        Anywhere
[ 4]    53                  ALLOW IN        Anywhere
[ 5]    80,443/tcp          ALLOW OUT       Anywhere
[ 6]    22/tcp              ALLOW OUT       Anywhere
[ 7]    123/udp             ALLOW OUT       Anywhere
[ 8]    53                  ALLOW OUT       Anywhere
[ 9]    80,443/tcp (v6)     ALLOW IN        Anywhere (v6)
[10]    22/tcp  (v6)        ALLOW IN        Anywhere (v6)
[11]    123/udp  (v6)       ALLOW IN        Anywhere (v6)
[12]    53  (v6)            ALLOW IN        Anywhere (v6)
[13]    80,443/tcp  (v6)    ALLOW OUT       Anywhere (v6)
[14]    22/tcp  (v6)        ALLOW OUT       Anywhere (v6)
[15]    123/udp  (v6)       ALLOW OUT       Anywhere (v6)
[16]    53  (v6)            ALLOW OUT       Anywhere (v6)

Router settings

You might also want to check the router settings and make sure all unused ports are disabled. Refer to the router's manual for more information.


What does ntp stand for

System Time

System time is critical for security protocols. Inaccuracies can compromise your server. The Network Time Protocol (NTP) maintains server time synchronised with reference computers.

Step-by-step guide

NTP server list

Back up the configuration file:

sudo cp --archive /etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf-COPY-$(date +"%Y%m%d%H%M%S")

Open the file:

sudo vi /etc/systemd/timesyncd.conf

Modify the content to access an open-source, public NTP server operated by the pool.ntp.org project or Ubuntu:

[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org
FallbackNTP=ntp.ubuntu.com

Save and close the file (:wq!). Finally, restart the systemd-timesyncd service and make sure it's running (the status should be Active):

sudo systemctl restart systemd-timesyncd
sudo systemctl status systemd-timesyncd

Time zone

Let's configure the server's time zone:

timedatectl | grep Time

Assuming you live near Budapest, the terminal should display something like:

Time zone: Europe/Budapest (CEST, +0200)

If not, set up the correct time zone (adjust accordingly):

sudo timedatectl set-timezone Europe/Budapest


NTP server IP

Remote Connection

To protect the remote connection to your server, begin by changing the standard SSH port. Limit login attempts and authorize only one user, the administrator. Track each login and prevent remote access to the root account for added security. Use authentication keys with passphrases instead of passwords. Enable automatic disconnection after inactivity. Remove short encryption keys. And add a legal banner to warn unauthorized users.

Step-by-step guide

SSH port & security settings

Open the new SSH port 2222 in the firewall (adjust accordingly):

sudo ufw allow 2222/tcp
sudo ufw allow out 2222/tcp

Back up the SSH configuration file. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --preserve /etc/ssh/sshd_config /etc/ssh/sshd_config.$(date +"%Y%m%d%H%M%S")

Open the SSH configuration file with the command:

sudo vi /etc/ssh/sshd_config

Delete the file content by typing :%d. Then enter or copy/paste the following content, while making sure to adjust settings where necessary (e.g. the admin user name):

# supported HostKey algorithms by order of preference:

HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com

# various security settings:

LogLevel                        VERBOSE     # log user's key fingerprint on login to have a clear audit track of which key was using to log in
Protocol                        2           # only use the newer, more secure protocol
PermitUserEnvironment           no          # don't let users set environment variables
PermitRootLogin                 no          # don't allow root login
PubkeyAuthentication            yes         # allow public key authentication
PasswordAuthentication          no          # login with password not allowed
PermitEmptyPasswords            no          # don't allow login if the account has an empty password
MaxAuthTries                    3           # maximum allowed attempts to login
MaxSessions                     2           # maximum number of open sessions
X11Forwarding                   no          # disable X11 forwarding
IgnoreRhosts                    yes         # ignore .rhosts and .shosts
UseDNS                          no          # verify hostname matches IP
ClientAliveCountMax             0           # maximum number of client alive messages sent without response
ClientAliveInterval             300         # timeout in seconds before a response request
AllowUsers                      gofossadmin # only allow user gofossadmin to login remotely (adjust accordingly)

# disable port forwarding:

AllowAgentForwarding            no
AllowTcpForwarding              no
AllowStreamLocalForwarding      no
GatewayPorts                    no
PermitTunnel                    no

# log sftp level file access (read/write/etc.):

Subsystem sftp  /usr/lib/openssh/sftp-server

# other security settings:

Compression                     no
PrintMotd                       no
TCPKeepAlive                    no
ChallengeResponseAuthentication no
UsePAM                          yes
AcceptEnv LANG LC_*
Banner              /etc/issue.net

Save and close the SSH configuration file (:wq!).

Since Ubuntu 22.10, SSHd uses socket-based activation and the Port option in the /etc/ssh/sshd_config file is unused. Create a new configuration file to make sure the new SSH port is correctly set to 2222 (or whatever port you chose):

sudo systemctl edit ssh.socket

Add the following content above the line ### Edits below this comment will be discarded:

[Socket]
ListenStream=
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222

Save and close the configuration file (:wq!). Then, restart the SSH server and verify that the new SSH port is set to 2222 (or whatever port you chose):

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket

Verify that the SSH service is running (Active) and the new SSH port is set to 2222 (or whatever port you chose):

sudo systemctl status ssh.socket
sudo ss -tlpn | grep ssh

Disconnect inactive connections

Make sure all remote SSH connections are disabled after 5 minutes of inactivity:

echo 'TMOUT=300' >> .bashrc
tail .bashrc

Remove short encryption keys

SSH uses the Diffie-Hellman algorithm to establish a secure connection. For security reasons, it's recommended to use keys which are at least 3072 bits long.

Back up the SSH configuration file. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --archive /etc/ssh/moduli /etc/ssh/moduli-COPY-$(date +"%Y%m%d%H%M%S")

Remove keys shorter than 3072 bits:

sudo awk '$5 >= 3071' /etc/ssh/moduli | sudo tee /etc/ssh/moduli.tmp
sudo mv /etc/ssh/moduli.tmp /etc/ssh/moduli

A warning message should be displayed to any user trying to remotely log into the server. Open the following two files:

sudo vi /etc/issue.net
sudo vi /etc/issue

Add a legal banner in each file, such as this one for example:

This system is for the use of authorised users only.

Individuals using this computer system without authority, or in excess of their authority, are subject to having all of their activities on this system monitored and recorded by system personnel.

In the course of monitoring individuals improperly using this system, or in the course of system maintenance, the activities of authorised users may also be monitored.

Anyone using this system expressly consents to such monitoring and is advised that if such monitoring reveals possible evidence of criminal activity, system personnel may provide the evidence of such monitoring to law enforcement officials.

Test configuration & close port 22

That's it! From now on you can securely use your client machine to log into the server. Just open a terminal, switch to the administrator account and connect to the server using your passphrase. Don't forget to adjust the username, IP address and SSH port as required:

su - gofossadmin
ssh -p 2222 gofossadmin@192.168.1.100

Try it a couple of times. If everything works as expected, close the unused port 22:

sudo ufw delete allow 22/tcp
sudo ufw delete allow out 22/tcp


What is server hardening

LAMP Security

MySQL databases power numerous web services. Apache is the most prevalent web server. And PHP is a widely-utilized programming langage. They all require proper configuration to bolster your server's security.

Step-by-step guide for MySQL hardening

MySQL hardening

Launch MySQL:

sudo mysql

Change the authentication parameters for the root user. Make sure to replace the string StrongPassword with a strong, unique password:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'StrongPassword';

Exit MySQL:

exit

Now, run the MySQL hardening script:

sudo mysql_secure_installation

Follow the on-screen instructions:

  • no need to change the root password, which was just defined
  • remove the anonymous user
  • forbid root to login remotely
  • remove the test database
  • reload the privileges table

Prevent unauthorised access

Back up the MySQL configuration file. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --archive /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf-COPY-$(date +"%Y%m%d%H%M%S")

Open the configuration file:

sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf

Disable remote MySQL access and prevent unauthorised access to local files by adding or adjusting the following two lines:

bind-address = 127.0.0.1
local-infile = 0

Save and close the MySQL configuration file (:wq!).

User permissions

Note that it's best practice to create a dedicated user with limited privileges for each application requiring MySQL access. For example the cloud storage or photo gallery we'll set up at a later stage will each have their own MySQL user. This way, applications remain isolated and can only access databases on a need-to-know basis.

Step-by-step guide for Apache hardening

Enable security modules

Install the following two Apache modules:

sudo apt install libapache2-mod-security2 libapache2-mod-evasive

Enable the security modules, as well as other common modules required for this tutorial, and restart Apache:

sudo a2enmod security2
sudo a2enmod evasive
sudo a2enmod rewrite
sudo a2enmod headers
sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_html
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod xml2enc
sudo a2enmod expires
sudo a2enmod env
sudo a2enmod dir
sudo a2enmod mime
sudo systemctl restart apache2

Some additional information about those modules:

Module Description
Security2 Protects your server from various attacks, such as SQL injection, session hijacking, cross-site scripting, bad user agents, etc.
Evasive Provides evasive actions in the event of a Denial of Service (DoS) or Distributed Denial of Service (DDoS) attack, or a brute force attack.
Rewrite Allows to rewrite URLs to enable re-directions, for example from http:// to https://
Headers Allows to control and modify HTTP requests and response headers.
SSL Enables encryption via SSL and TLS.
Proxy, proxy_html, proxy_http Creates a proxy/gateway for your server, ensures that links work for users outside the proxy, and proxies HTTP and HTTPS requests.
Xml2enc Provides enhanced internationalisation support.
Expires Enhances page load time by determining how long an image will be stored by the browser.

Hide sensitive information

Per default, the server sends HTTP headers containing information on the Apache version, modules, operating system, and so on. This data can be used to exploit vulnerabilities. Create a custom Apache configuration file to hide such sensitive information:

sudo vi /etc/apache2/conf-available/custom.conf

Add the following content:

ServerTokens        Prod
ServerSignature     Off
TraceEnable         Off
Options all -Indexes
Header always unset X-Powered-By

Save and close the file (:wq!).

Apply the custom configuration, check for syntax errors (the terminal should display OK) and restart Apache:

sudo a2enconf custom.conf
sudo apachectl configtest
sudo systemctl restart apache2

Some additional information about those settings:

Setting Description
ServerTokens Prod Only return "Apache" in the server header.
ServerSignature Off Hide the server version on pages generated by Apache.
TraceEnable Off Disable the HTTP TRACE method, which can expose your server to cross-site scripting attacks.
Options all -Indexes Disable directory views.
Header always unset X-Powered-By Hide the information "X-Powered-By ..." and avoid displaying which software is used.

Configure ModSecurity

ModSecurity is an open-source web application firewall. This tool can be configured to protect your server from various attacks. Open the configuration file:

sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo vi /etc/modsecurity/modsecurity.conf

Modify/add the following parameters:

SecRuleEngine                   On
SecResponseBodyAccess           Off
SecRequestBodyLimit             8388608
SecRequestBodyNoFilesLimit      131072
SecRequestBodyInMemoryLimit     262144

Save and close the file (:wq!).

Some additional information about those settings:

Setting Description
SecRuleEngine On Enable ModSecurity using the basic default rules.
SecResponseBodyAccess Off Disable response body buffering to use less RAM and CPU.
SecRequestBodyLimit 8388608 Set the maximum request body size accepted for buffering to 8 MB.
SecRequestBodyNoFilesLimit 131072 Set the maximum data size accepted for buffering to 128 KB.
SecRequestBodyInMemoryLimit 262144 Set the maximum request body size stored in RAM to 256 KB.

Enable the latest OWASP ModSecurity Core Rule Set (CRS), a set of generic attack detection rules maintained on GitHub:

sudo rm -rf /usr/share/modsecurity-crs
sudo apt install git
sudo git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git /usr/share/modsecurity-crs
cd /usr/share/modsecurity-crs
sudo mv crs-setup.conf.example crs-setup.conf

Back up the ModSecurity configuration file. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --archive /etc/apache2/mods-enabled/security2.conf /etc/apache2/mods-enabled/security2.conf-COPY-$(date +"%Y%m%d%H%M%S")

Open the configuration file:

sudo vi /etc/apache2/mods-enabled/security2.conf

Delete the content with the command :%d and add or copy/paste the following lines:

<IfModule security2_module>
    SecDataDir /var/cache/modsecurity
    IncludeOptional /etc/modsecurity/*.conf
    IncludeOptional /usr/share/modsecurity-crs/*.conf
    IncludeOptional /usr/share/modsecurity-crs/rules/*.conf
</IfModule>

Save and close the file (:wq!). Check for syntax errors (the terminal should display OK) and restart Apache to apply the new configuration:

sudo apachectl configtest
sudo systemctl restart apache2

Back up the ModEvasive configuration file. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --archive /etc/apache2/mods-enabled/evasive.conf /etc/apache2/mods-enabled/evasive.conf-COPY-$(date +"%Y%m%d%H%M%S")

Configure ModEvasive to protect your server from Denial of Service (DoS), Distributed Denial of Service (DDoS) and brute force attacks.

Open the configuration file:

sudo vi /etc/apache2/mods-enabled/evasive.conf

Modify or add the following parameters:

<IfModule mod_evasive20.c>
    DOSPageCount        5
    DOSSiteCount        50
    DOSPageInterval     1
    DOSSiteInterval     1
    DOSBlockingPeriod   600
    DOSWhitelist        192.168.1.XX 10.8.0.YY
    DOSLogDir           "/var/log/mod_evasive"
</IfModule>

Save and close the file (:wq!). Some additional information about those settings:

Setting Description
DOSPageCount 5 Defines how many times a single page can be requested by the same IP in a short time. If one IP asks for the same URL more than 5 times, it is treated as suspicious.
DOSSiteCount 50 Defines how many total requests an IP can make to the site. If one IP makes more than 50 requests to any pages combined, it is treated as suspicious.
DOSPageInterval 1 Defines the time window for DOSPageCount. The 5 requests must happen within 1 second to trigger protection.
DOSSiteInterval 1 Defines the time window for DOSSiteCount. The 50 requests must happen within 1 second to trigger protection.
DOSBlockingPeriod 600 Defines how long an IP is blocked after being flagged. The offending IP is blocked for 600 seconds (10 minutes).
DOSWhitelist 192.168.1.XX 10.8.0.YY Defines which IP addresses are always allowed. Requests from 192.168.1.XX or 10.8.0.YY are never blocked, even if they exceed the limits (adjust IPs according to your setup!).
DOSLogDir "/var/log/mod_evasive" Defines where mod_evasive writes its log files. Blocked IPs and events are recorded in this directory.

The following commands create a directory for the log files, check for syntax errors (the terminal should display OK), restart Apache and verify if the status is Active:

sudo mkdir /var/log/mod_evasive
sudo chown -R www-data: /var/log/mod_evasive
sudo apachectl configtest
sudo systemctl restart apache2
sudo systemctl status apache2
Step-by-step guide PHP hardening

PHP version

PHP supports various versions. At the time of writing, PHP 8.3 is included by default in Ubuntu's 24.04 repositories. Check which version is installed on your system:

php -v

PHP modules

Install some common PHP modules required for this tutorial, applying the same version number as above:

sudo apt install php8.3-common php8.3-mbstring php8.3-xmlrpc php8.3-gd php8.3-xml php8.3-intl php8.3-mysql php8.3-cli php8.3-ldap php8.3-zip php8.3-curl php8.3-pgsql php8.3-opcache php8.3-sqlite3

PHP security settings

The php.ini configuration files contain all relevant PHP security settings. First, let's find out where these files are stored:

sudo find / -name php.ini

Depending on the PHP version, the command might return something like:

/etc/php/8.3/apache2/php.ini
/etc/php/8.3/cli/php.ini

The /etc/php/8.3/apache2/php.ini file is used by the Apache server, while the /etc/php/8.3/cli/php.ini file is used by the CLI PHP program.

Back up both configuration files. If something goes wrong, you'll be able to recover the initial settings:

sudo cp --archive /etc/php/8.3/apache2/php.ini /etc/php/8.3/apache2/php.ini-COPY-$(date +"%Y%m%d%H%M%S")
sudo cp --archive /etc/php/8.3/cli/php.ini /etc/php/8.3/cli/php.ini-COPY-$(date +"%Y%m%d%H%M%S")

Open the first configuration file:

sudo vi /etc/php/8.3/apache2/php.ini

Modify or add the following parameters:

expose_php          =   Off
allow_url_fopen     =   Off
allow_url_include   =   Off
display_errors      =   Off
mail.add_x_header   =   Off
disable_functions   =   show_source,system,passthru,phpinfo,proc_open,allow_url_fopen
max_execution_time  =   90
max_input_time      =   90
memory_limit        =   1024M

Save and close the file (:wq!).

Open the second configuration file:

sudo vi /etc/php/8.3/cli/php.ini

Modify or add the following parameters:

expose_php = Off
allow_url_fopen = Off

Save and close the file (:wq!).

Some additional information about those settings:

Setting Description
expose_php = Off Hides the PHP version in headers.
allow_url_fopen = Off Disables remote PHP code execution to reduce code injection vulnerabilities.
allow_url_include = Off Disables remote PHP code execution to reduce code injection vulnerabilities.
display_errors = Off Disables error display.
mail.add_x_header = Off Removes PHP header from emails.
disable_functions = show_source, system, passthru, phpinfo, proc_open, allow_url_fopen Disables potentially harmful PHP functions. The following three functions have not been disabled, as this breaks Pihole v5.2: exec, shell_exec, popen. The curl_exec function hasn't been disabled, as this breaks Nextcloud.
max_execution_time = 90 Sets the maximum time a script is allowed to run (in seconds).
max_input_time = 90 Sets the maximum time a script can parse data (in seconds).
memory_limit = 1024M Sets the maximum amount of memory allocated to a script.


Unattended upgrades Ubuntu

Automatic Updates

Keep your server up-to-date to minimize vulnerabilities. Activate automatic security updates on Ubuntu to streamline the process.

Step-by-step guide

Enable automatic updates:

sudo apt install unattended-upgrades

Create a configuration file:

sudo vi /etc/apt/apt.conf.d/51myunattended-upgrades

Add the following content to configure unattended upgrades:

// Enable the update/upgrade script (0=disable)
APT::Periodic::Enable "1";

// Do "apt-get update" automatically every n-days (0=disable)
APT::Periodic::Update-Package-Lists "1";

// Do "apt-get upgrade --download-only" every n-days (0=disable)
APT::Periodic::Download-Upgradeable-Packages "1";

// Do "apt-get autoclean" every n-days (0=disable)
APT::Periodic::AutocleanInterval "7";

// Send report mail to root
//     0:  no report             (or null string)
//     1:  progress report       (actually any string)
//     2:  + command outputs     (remove -qq, remove 2>/dev/null, add -d)
//     3:  + trace on
APT::Periodic::Verbose "2";
APT::Periodic::Unattended-Upgrade "1";

// Automatically upgrade packages from these
Unattended-Upgrade::Origins-Pattern {
    "o=Ubuntu,a=stable";
    "o=Ubuntu,a=stable-updates";
    "origin=Ubuntu,codename=${distro_codename},label=Ubuntu-Security";
};

// You can specify your own packages to NOT automatically upgrade here
Unattended-Upgrade::Package-Blacklist {
};

// Run dpkg --force-confold --configure -a if a unclean dpkg state is detected to true to ensure that updates get installed even when the system got interrupted during a previous run
Unattended-Upgrade::AutoFixInterruptedDpkg "true";

//Perform the upgrade when the machine is running because we wont be shutting our server down often
Unattended-Upgrade::InstallOnShutdown "false";

// Send an email to this address with information about the packages upgraded.
Unattended-Upgrade::Mail "root";

// Always send an e-mail
Unattended-Upgrade::MailOnlyOnError "false";

// Remove all unused dependencies after the upgrade has finished
Unattended-Upgrade::Remove-Unused-Dependencies "true";

// Remove any new unused dependencies after the upgrade has finished
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";

// DO NOT automatically reboot WITHOUT CONFIRMATION if the file /var/run/reboot-required is found after the upgrade.
Unattended-Upgrade::Automatic-Reboot "false";

// DO NOT automatically reboot even if users are logged in.
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";

Save and close the file (:wq!). Perform a test run to make sure everything is working:

sudo unattended-upgrade -d --dry-run


Ubuntu server security