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.
/tmprestrictions: Disablemount_option_tmp_noexecif 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.