Hardening Ubuntu 24.04: Production Web Server Security Guide

A step-by-step guide to hardening Ubuntu 24.04 for production web environments using SSH keys, UFW, CIS Level 1 baselines, and Fail2ban intrusion prevention.

Deploying a new Linux Virtual Machine (VM) to serve as a web server requires a deliberate approach to security from the moment it comes online. The public internet is a hostile environment; within minutes of assigning a public IP address, automated scanners will begin probing your server for vulnerabilities. A standard application installation without a hardened foundation leaves your infrastructure exposed.

Security is a series of logical layers designed to mitigate risk and increase the complexity of an attack. This approach is called Defense in Depth. For a production environment, relying solely on a basic firewall is insufficient. You should aim to apply strict compliance baselines like CIS Level 1.

Applying these baselines requires a specific order of operations. If you harden the OS blindly, you risk severing your administrative access and breaking your application stack.

Here is a systematic guide to securely deploying an Ubuntu 24.04 web server.

Phase 1: Identity and Access (Closing the Front Door)

When you first deploy a VM, you define an administrative user. We need to ensure that user account is strictly controlled.

1. Enforce SSH Key Authentication

Cryptographic keys provide significantly stronger security than passwords. Generate an SSH key pair on your local machine, place the public key on the server, and disable password-based authentication completely.

Edit your /etc/ssh/sshd_config file:

sudo nano /etc/ssh/sshd_config

Ensure these directives are set:

PasswordAuthentication no
PermitRootLogin no

Restart the SSH service:

sudo systemctl restart ssh

Phase 2: The Network Firewall

Minimal Ubuntu 24.04 images often omit non-essential packages, meaning you may need to install a host-based firewall manually. We use UFW (Uncomplicated Firewall) to establish a strict network perimeter.

The most secure network architecture operates on a zero-trust model. We configure the firewall to block everything by default and explicitly allow only required traffic.

1. Install UFW and Allow SSH (Do not skip this!)

sudo apt update
sudo apt install -y ufw
sudo ufw allow ssh

2. Allow Web and Essential Outbound Traffic

Your server needs to accept web traffic, resolve domains, sync its clock, download updates, and maintain its IP lease via DHCP.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow out 53       # DNS
sudo ufw allow out 123/udp  # NTP (Time sync)
sudo ufw allow out 80/tcp   # HTTP out
sudo ufw allow out 443/tcp  # HTTPS out
sudo ufw allow out 67,68/udp # DHCP out

3. Set Default Deny and Enable

sudo ufw default deny incoming
sudo ufw default deny outgoing
sudo ufw enable

Phase 3: CIS Level 1 Hardening (The Baseline)

CIS Level 1 is a comprehensive checklist of aggressive security configurations. Blindly applying a default CIS script to a Linux VM can disrupt SSH connectivity and break necessary system integrations.

1. Create a "Lifeline" Terminal

Take a snapshot of your VM in your hypervisor or management console. Open two SSH sessions. Leave the first one open as a root shell. Do your work in the second. If the script disrupts your SSH configuration, your first terminal remains active so you can revert the changes.

2. Install Ubuntu Security Guide (USG) and Generate a Profile

sudo apt install -y usg
sudo usg generate-tailoring cis_level1_server tailored_profile.xml

3. Edit the Tailoring File

Open the file (sudo nano tailored_profile.xml). Use Ctrl+W to search for specific rules and change selected="true" to selected="false" for configurations that conflict with your operational requirements:

  • Firewall rules: Since UFW is manually configured in Phase 2, set UFW CIS rules to false to prevent the script from wiping your explicit IP exceptions.
  • SSH configurations: Adjust SSH keep-alive or timeout behaviors if they conflict with your infrastructure's load balancers or management consoles.
  • /tmp restrictions: Disable mount_option_tmp_noexec if your web stack requires executing temporary setup files.

4. Apply the Baseline

sudo usg fix --tailoring-file tailored_profile.xml

Once applied, reboot the server to ensure all kernel-level changes take effect cleanly:

sudo reboot

Phase 4: The Web Stack (Apache, MariaDB, PHP)

With a hardened foundation in place, install the application layer.

1. Install the LAMP Stack

sudo apt install -y apache2 mariadb-server php libapache2-mod-php php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip

2. Ensure Apache is Active

Depending on your base image or the security baselines applied, newly installed services might not start automatically or could be masked. To ensure Apache is unmasked, enabled, and running:

sudo systemctl unmask apache2
sudo systemctl enable apache2
sudo systemctl start apache2

3. Secure the Database

Create a dedicated database and user for WordPress:

sudo mysql
CREATE DATABASE noudar DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'yourusername'@'localhost' IDENTIFIED BY 'YourStrongPasswordHere!';
GRANT ALL ON noudar.* TO 'yourusername'@'localhost';
FLUSH PRIVILEGES;
EXIT;

4. Download WordPress and Set Strict Permissions

Download the application in your home directory, move the files, and apply the Principle of Least Privilege. The web server should only read your files, not own them.

cd ~
wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
sudo cp -a ~/wordpress/. /var/www/html/
sudo rm /var/www/html/index.html

sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

5. Enable Overrides for WordPress URLs

sudo a2enmod rewrite
sudo nano /etc/apache2/sites-available/000-default.conf

Add this configuration below DocumentRoot /var/www/html:

<Directory /var/www/html>
    AllowOverride All
</Directory>

Restart Apache:

sudo systemctl restart apache2

Production Note on WordPress URLs: If you set up WordPress via its IP address initially, it may redirect your domain name back to that IP. To resolve this, open /var/www/html/wp-config.php and add the following lines above the /* That's all, stop editing! */ comment:

define( 'WP_HOME', 'https://yourdomain.com' );
define( 'WP_SITEURL', 'https://yourdomain.com' );

In production, always use the actual scheme your site serves (e.g., https:// instead of http://). Note that using this method hard-codes these values into your configuration, overriding any database settings.


Phase 5: Active Defense (Fail2ban)

Because your firewall allows port 80/443 traffic, attackers can attempt to brute-force your WordPress login. Fail2ban acts as an active intrusion prevention system, reading your Apache logs and dynamically banning malicious IPs.

1. Install Fail2ban

sudo apt install -y fail2ban
sudo systemctl enable fail2ban

2. Create the Jail Configuration

sudo nano /etc/fail2ban/jail.local

Paste this configuration:

[DEFAULT]
bantime  = 86400
findtime  = 600
maxretry = 5
banaction = ufw

[wordpress-hardened]
enabled = true
port = http,https
filter = wordpress-hardened
logpath = /var/log/apache2/access.log
maxretry = 3

3. Create the Filter

sudo nano /etc/fail2ban/filter.d/wordpress-hardened.conf

Paste this definition to catch invalid logins and XML-RPC attacks:

[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
            ^<HOST> .* "POST /xmlrpc.php
ignoreregex =

Restart the service:

sudo systemctl restart fail2ban

Conclusion

You have successfully established strict network routing, applied a recognized compliance baseline, and deployed an actively defended web stack. Security requires ongoing attention. Maintain system updates, enforce strong password policies, and regularly audit your logs and Fail2ban status (sudo fail2ban-client status wordpress-hardened) to ensure your environment remains secure.