Table of Contents
Exposing an Ubuntu Server to the Internet can pose some security risks. Malicious bots and scanners are crawling known IPs and ports to find security vulnerabilities. While Ubuntu Server is rather secure by default, here are some tips and tricks on how to improve security of a standard Ubuntu Server installation.
If you check the SSH and web server logs on a freshly created server, you might be surprised to see the amount of traffic that is sent. Bots are scouring the Internet looking for open servers and vulnerable software to exploit. A compromised server might become part of a botnet, data might be exfiltrated or ransomware installed. Therefore, special care must be taken to ensure that the server is secure.
For best results, I recommend sticking to an LTS (Long Term Support) version of Ubuntu. This provides the longest window of security updates (5 years) without the need to upgrade the server. Whereas, standard versions are only supported for 9 months.
Securing the login
Creating a non-root user
Often, when Ubuntu servers are provisioned, you are given the root
login through SSH. This is necessary to get started with the server, however you should create a non-root user as soon as possible.
This can be done easily using the adduser
command as so:
sudo adduser [mynewuser]
Avoid creating a user with a common login such as admin
, user
, sysadmin
, etc. Use a unique or uncommon name to make probing for logins more difficult.
Make the user able to use sudo
by adding the user to the sudo
group:
sudo usermod -aG sudo [mynewuser]
Using a private key to login
Next, you shouldn't log in to the server using a password. Instead, consider using a private key. A key with a few thousand bits of entropy is impossible to guess, unlike passwords which can be bruteforced, guessed from a dictionary or stolen from password dumps.
Log in as your custom user and run the following command to generate a key pair:
ssh-keygen
For extra security, set a passphrase on your key.
This will create two files: id_ed25519
(the private key) and id_ed25519.pub
(the public key). These keys use elliptic curve cryptography which is more secure than traditional RSA keys. If you're using an older Ubuntu version you may get an id_rsa
key file instead. This is not necessarily a problem though.
To allow logging with this key, we need to add the public key to the authorized_keys
file and adjust the file permissions accordingly.
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
View the private key and copy it to your local machine:
cat ~/.ssh/id_ed25519
Disabling root and password login
Before you proceed, make sure you can log in to your user account with the previously generated private key.
ssh [user]@[host] -i ~/somewhereonmylocalmachine/id_ed25519
It's a good practice to disable logging in with passwords or as the root
user.
To do this, edit the /etc/ssh/sshd_config
:
sudo vim /etc/ssh/sshd_config
# or if you prefer
sudo nano /etc/ssh/sshd_config
Set the following values in the file:
PermitRootLogin no
PasswordAuthentication no
Finally, restart the SSH server to apply the changes:
sudo systemctl restart ssh.service
SSH Two-Factor Authentication
Alternatively, if you don't want to use a private key, you can enable two-factor authentication on your login. This means that along with your password, you will need to provide a 6-digit code generated from a TOTP client (Android or iOS device).
Install the google-authenticator
package and start the setup:
sudo apt install libpam-google-authenticator
google-authenticator
Say Yes to "Do you want authentication tokens to be time-based?".
You will be shown an ASCII QR code in the terminal that you can scan in an app. Make sure you use a 2FA app the allows backups such as Stratum, this means that you won't lose access to your server if you lose your phone.
#
# ASCII QR code displayed here
#
Your new secret key is: MU5GW5A2RY7M5BZ4XIJINWUYP4
Enter code from app (-1 to skip): 912198
Code confirmed
Your emergency scratch codes are:
31487191
68248222
21255915
96687320
65438781
You will be asked to confirm proper setup by entering the code generated on your device. You can then provide the following recommended answers to the questions:
- Do you want me to update your "/home/myuser/.google_authenticator" file? (y/n): Yes
- Do you want to disallow multiple uses of the same authentication token?.. (y/n): Yes
- By default, a new token is generated every 30 seconds by the mobile app... (y/n): No
- Do you want to enable rate-limiting? (y/n) : Yes
To enable the verification code prompt, edit the following file and add the next line:
sudo vim /etc/pam.d/sshd
auth required pam_google_authenticator.so
Edit your SSH daemon configuration to enable interactive authentication and disable plain passwords:
sudo vim /etc/ssh/sshd_config
KbdInteractiveAuthentication yes
PasswordAuthentication no
Finally, restart the SSH daemon to apply changes:
sudo systemctl restart ssh.service
Now when you log in, you will need to first provide your password and then a 6-digit code from the authenticator app.
Enabling UFW (Uncomplicated FireWall)
If your beard is not grey enough to understand iptables, you can use UFW (Uncomplicated FireWall) instead. As the name suggests, this is a simpler way of managing a firewall if your needs are relatively simple.
To get a list of available profiles, run the sudo ufw app list
command:
myuser@myserver:~$ sudo ufw app list
Available applications:
Nginx Full
Nginx HTTP
Nginx HTTPS
OpenSSH
Allow OpenSSH connections through the firewall.
sudo ufw allow OpenSSH
And then, enable the firewall:
sudo ufw enable
If you didn't get kicked from the server, that means you did it in the right order. Any other connections will now be refused.
Assuming you have NGINX installed, you can enable HTTP and HTTPS requests by enabling the Nginx Full
profile.
Blocking bruteforce logins with Fail2Ban
Fail2Ban is a daemon that scans for incorrect login attempts through SSH and web servers. If a malicious client is detected, it will block that IP address for a set period of time. To install it, run the following command:
sudo apt install fail2ban
Then enable and start the Fail2Ban daemon:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
By default, Fail2Ban is configured to look for SSH authentication failures. If you want it to scan for authentication failures in NGINX or Apache, you must configure the service. More information can be found in the Fail2Ban wiki.
Enable Unattended Upgrades
On an up-to-date Ubuntu install, unattended upgrades should be enabled by default. This allows the system to apply security patches automatically without having to do apt upgrade
manually.
Make sure than the unattended-upgrades
package is installed using the following command:
sudo apt install unattended-upgrades
If the package is already installed you won't be prompted to configure it. Run the following command and select 'Yes' to enable auto upgrades.
sudo dpkg-reconfigure unattended-upgrades
Enable Livepatch and ESM (Ubuntu Pro)
In the same vein, you can enable automatic kernel patching using Livepatch. This is a feature of Ubuntu Pro that performs automatic kernel updates without rebooting. Also with Ubuntu Pro you can use ESM (Expanded Security Maintenance) which provides additional security updates for installed software.
Ubuntu Pro is a paid service, however you can enable it for free on up to 5 machines for personal use. To get a token you must first subscribe to Ubuntu Pro.
Once you have a token, you can enable the service with the following command:
sudo pro attach [mytoken]
You can check the status using this command:
sudo pro status
Web server hardening
The primary use for many Ubuntu and Linux servers is as a web server. Here are a few tips for this use case.
Enable HTTPS
Websites should be served over a secure connection. In the past, this involved purchasing an SSL certificate. Thankfully, Let's Encrypt provides a free and convenient way of enabling HTTPS on your website.
First install certbot, this will manage our TLS certificates:
sudo snap install --classic certbot
Create a symbolic link to certbot in somewhere on the PATH.
sudo ln -s /snap/bin/certbot /usr/bin/certbot
You can request an HTTPS certificate for NGINX using the following command. Certbot will modify the NGINX configuration to use the generated certificate.
sudo certbot --nginx
Certbot will automatically renew HTTPS certificates for you. However, you can test this using the following command to be sure it works:
sudo certbot renew --dry-run
Disable Server header
If you're using NGINX (or Apache), you should disable the Server
header. This returns the server name a version when an HTTP request is made. There is no benefit to disclosing this information, so it should be disabled.
Edit your NGINX configuration:
sudo vim /etc/nginx/nginx.conf
Change the following directive:
server_tokens off;
Use rate limiting and connection limits
If your server is exposed directly to the Internet, in order to mitigate attacks on your web server, consider enabling the following:
- Rate limits: limit the number of requests a client can make in a given period
- Connection limits: limit the number of simultaneous connections a client can make
- Backend limits: if you're using NGINX as a reverse proxy, limit the number of connections to your backend server
These features are covered in depth in the following articles on the NGINX blog: Rate Limiting with NGINX and Mitigating DDoS Attacks with NGINX.
Cloudflare firewall and caching
Cloudflare is a service that provides bot and DDoS protection for websites. The free tier is generous and provides a decent level of protection. Setting up Cloudflare for your server involves changing the DNS nameservers to allow routing traffic through the Cloudflare network.
The official documentation provides an overview of how to add a domain.
Origin mTLS
You can enable mTLS (Mutual TLS) between the Cloudflare network and your server. This ensures that the traffic your server receives has been sent through Cloudflare.
First enable Authenticated Origin Pulls in the Cloudflare dashboard: SSL/TLS → Origin Server → Authenticated Origin Pulls
Next, download the Cloudflare certificate authority file on the server:
wget https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
sudo mv authenticated_origin_pull_ca.pem /etc/nginx/cloudflare.pem
Then, edit the NGINX configuration (or a virtualhost) to only accept signed requests:
sudo vim /etc/nginx/nginx.conf
http {
...
# Cloudflare mTLS
ssl_verify_client on;
ssl_client_certificate /etc/nginx/cloudflare.pem;
}
Run software in Docker containers
Modern software practices involve running applications in containers, this provides a great deal of convenience but also a high level of security. Containers are isolated from the host machine and have very little access if configured correctly. This means that a vulnerable application can't do damage to the system or other containers.
For most application servers, I recommend using NGINX as a reverse proxy on the host and proxying to a Docker container on a port that's not exposed through the firewall (such as 8080).
Rootless containers
By default, Docker containers run as root
. Consider using a custom user for the application, this limits the impact of a compromised application within a container. For most applications, you can simply set the USER
directive in your Dockerfile to something other than root
.