• This field is for validation purposes and should be left unchanged.
  • LET'S TALK!

    Fill in the form below to make an enquiry or find my contact details on my contact page.

Freelance WordPress Developer

Install WordPress on Ubuntu 24.04

PREREQUISITES:

In order to follow this guide, you should have a server with Ubuntu 24.04 (LTS) installed with a minimum of 2GB RAM and 20GB hard drive space, and access to sudo privileges. You will also need a domain name pointed to your server’s IP address.

 

Step 1: Update Ubuntu 24.04 (LTS)

Before we install any software, it’s always a good idea to update repository and software packages. SSH into your Ubuntu 24.04 (LTS) server and enter the below commands.

sudo apt update
sudo apt upgrade -y

 

Step 2: Configure Firewall (UFW)

Before installing services, let’s configure the firewall to allow only necessary ports. Ubuntu comes with UFW (Uncomplicated Firewall) pre-installed.

# Allow SSH (important - do this first!)
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable

# Check firewall status
sudo ufw status

 

Step 3: Install NGINX Web Server

NGINX is a high performance web server. It also can be used as a reverse proxy. Enter this command to install NGINX Web server.

sudo apt install nginx -y

After it’s installed, we can enable Nginx to auto-start when Ubuntu is booted by running the following command.

sudo systemctl enable nginx

Then start Nginx with this command:

sudo systemctl start nginx

Now check out its status.

systemctl status nginx

 

Step 4: Install MariaDB

MariaDB is a drop-in replacement for MySQL. It is developed by former members of MySQL team who concerned that Oracle might turn MySQL into a closed-source product. Many Linux distributions (Arch Linux, Fedora etc), companies and organisations (Google, Wikipedia) have migrated to MariaDB.

sudo apt install mariadb-server mariadb-client -y

To enable MariaDB to automatically start when Ubuntu is rebooted:

sudo systemctl enable mariadb

Start MariaDB service:

sudo systemctl start mariadb

Check its status:

systemctl status mariadb

Now run the post-installation security script:

sudo mysql_secure_installation

When it asks you to enter MariaDB root password, press Enter because you have not set the root password yet. Then enter Y to set the root password for MariaDB server. Next, press Enter to answer all the remaining questions with the default (Yes). This will remove anonymous user, disable remote root login and remove test database. This step is a basic requirement for MariaDB database security.

 

Step 5: Optimize MariaDB Configuration

Create a custom MariaDB configuration file for WordPress optimization:

sudo nano /etc/mysql/mariadb.conf.d/99-wordpress.cnf

Add the following configuration:

[mysqld]
# WordPress optimizations
max_allowed_packet = 64M
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# Connection settings
max_connections = 200
wait_timeout = 60
interactive_timeout = 60

# Query cache (if needed for older WordPress versions)
query_cache_type = 1
query_cache_size = 32M
query_cache_limit = 2M

Restart MariaDB to apply changes:

sudo systemctl restart mariadb

 

Step 6: Create Database for WordPress Website

Change database name and user and remember to change the password to a secure one. Use a strong password generator for production sites.

mysql -u root -p
CREATE DATABASE nunosarmento DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

GRANT ALL ON nunosarmento.* TO 'nunosarmentouser'@'localhost' IDENTIFIED BY 'YourSecurePassword123!';

FLUSH PRIVILEGES;

EXIT;

Note: Using utf8mb4 instead of utf8 ensures full Unicode support, including emoji characters.

 

Step 7: Install and Setup PHP 8.3

Enter the following commands to install repository, PHP 8.3 and PHP 8.3 extensions.

PHP 8.3 Installation

sudo apt install software-properties-common -y

sudo add-apt-repository ppa:ondrej/php -y

sudo apt update

sudo apt install php8.3 php8.3-fpm php8.3-mysql php8.3-common php8.3-gd php8.3-cli php8.3-curl php8.3-mbstring php8.3-xml php8.3-xmlrpc php8.3-zip php8.3-intl php8.3-imagick php8.3-bcmath php8.3-soap -y

After the installation has completed, confirm that PHP 8.3 has installed correctly with this command:

php-fpm8.3 -v

Configure the user and group that the service will run under:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

Verify these settings (they should already be correct, but confirm):

user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data

Configure PHP for WordPress by changing some values in php.ini:

sudo nano /etc/php/8.3/fpm/php.ini

Search for and modify these values:

file_uploads = On
allow_url_fopen = On
upload_max_filesize = 100M
post_max_size = 100M
memory_limit = 256M
max_execution_time = 360
max_input_vars = 5000
max_input_time = 1000
display_errors = Off
log_errors = On
error_log = /var/log/php8.3-fpm-errors.log
expose_php = Off

Security Enhancement: Disable dangerous PHP functions:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,show_source

Reload php8.3-fpm:

sudo systemctl reload php8.3-fpm

Check status:

systemctl status php8.3-fpm

To test the CLI version of PHP:

php --version

 

Step 8: Install WP-CLI

WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installations and much more, without using a web browser.

cd /tmp

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

chmod +x wp-cli.phar

sudo mv wp-cli.phar /usr/local/bin/wp

wp --info

 

Step 9: Create NGINX Virtual Host

Create NGINX configuration file (change the domain name to your domain name):

sudo nano /etc/nginx/sites-available/nuno-sarmento.com

Paste the following text into the file. Replace “server_name” with your actual domain name and the website “root” location:

server {
    listen 80;
    listen [::]:80;
    server_name nuno-sarmento.com www.nuno-sarmento.com;

    root /var/www/nuno-sarmento.com;
    index index.php index.html index.htm;
    
    # Log files
    access_log /var/log/nginx/nuno-sarmento.com.access.log;
    error_log /var/log/nginx/nuno-sarmento.com.error.log;

    # Max upload size
    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Save and close the file. Then create symlink to nginx sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/nuno-sarmento.com /etc/nginx/sites-enabled/

Create your virtual host website directory:

sudo mkdir -p /var/www/nuno-sarmento.com

Set permissions to the virtual host directory:

sudo chown -R www-data:www-data /var/www/nuno-sarmento.com

Check if your NGINX configuration is valid:

sudo nginx -t

If the test is successful, reload NGINX:

sudo systemctl reload nginx

 

Step 10: Install WordPress

cd /tmp

curl -LO https://wordpress.org/latest.tar.gz

tar xzvf latest.tar.gz

cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php

sudo cp -a /tmp/wordpress/. /var/www/nuno-sarmento.com/

sudo chown -R www-data:www-data /var/www/nuno-sarmento.com

cd /var/www/nuno-sarmento.com

sudo find . -type d -exec chmod 755 {} \;
sudo find . -type f -exec chmod 644 {} \;

Edit the WordPress wp-config.php with the details from Step 6:

sudo nano /var/www/nuno-sarmento.com/wp-config.php

Update the database credentials:

define( 'DB_NAME', 'nunosarmento' );
define( 'DB_USER', 'nunosarmentouser' );
define( 'DB_PASSWORD', 'YourSecurePassword123!' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

Generate and add WordPress Salt Keys:
Visit https://api.wordpress.org/secret-key/1.1/salt/ and copy the generated keys, then replace the dummy values in wp-config.php.

Add additional security configurations to wp-config.php:

# Add these lines before "That's all, stop editing!"

# Security enhancements
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', false );
define( 'WP_POST_REVISIONS', 5 );
define( 'AUTOSAVE_INTERVAL', 300 );
define( 'WP_AUTO_UPDATE_CORE', 'minor' );

# Force SSL for admin
define( 'FORCE_SSL_ADMIN', true );

# Increase memory limit
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

Set proper permissions for wp-config.php:

sudo chmod 640 /var/www/nuno-sarmento.com/wp-config.php
sudo chown www-data:www-data /var/www/nuno-sarmento.com/wp-config.php

 

Step 11: Create Common WordPress Nginx Rules

In order to secure/harden your WordPress you will need to add some nginx rules. Create a common directory for reusable Nginx configuration:

sudo mkdir -p /etc/nginx/common
sudo nano /etc/nginx/common/wordpress.conf

Add comprehensive WordPress-specific Nginx rules:

# WordPress security and optimization rules

# Deny access to sensitive files
location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
}

location ~* wp-config.php {
    deny all;
}

location ~* /\.ht {
    deny all;
}

location ~* /readme\.html {
    deny all;
}

location ~* /readme\.txt {
    deny all;
}

location ~* /license\.txt {
    deny all;
}

location ~* /xmlrpc\.php {
    deny all;
}

# Deny access to WordPress installation files
location = /wp-admin/install.php {
    deny all;
}

location = /wp-admin/upgrade.php {
    deny all;
}

# Cache static files
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires max;
    log_not_found off;
    access_log off;
    add_header Cache-Control "public, immutable";
}

# Deny access to backup and log files
location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)$ {
    deny all;
}

# Prevent access to hidden files
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

# WordPress: deny wp-content, wp-includes php files
location ~* ^/(?:wp-content|wp-includes)/.*\.php$ {
    deny all;
}

# WordPress: deny general stuff
location ~* ^/(?:xmlrpc\.php|wp-links-opml\.php|wp-config\.php|wp-config-sample\.php|wp-comments-post\.php|readme\.html|license\.txt)$ {
    deny all;
}

# Rate limiting for wp-login.php
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=2r/m;
location = /wp-login.php {
    limit_req zone=wplogin burst=2 nodelay;
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
}

# Rate limiting for wp-cron.php
location = /wp-cron.php {
    limit_req zone=wplogin;
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
}

Update your virtual host configuration to include these rules:

sudo nano /etc/nginx/sites-available/nuno-sarmento.com

Add the include directive after the log files section:

server {
    listen 80;
    listen [::]:80;
    server_name nuno-sarmento.com www.nuno-sarmento.com;

    root /var/www/nuno-sarmento.com;
    index index.php index.html index.htm;
    
    # Log files
    access_log /var/log/nginx/nuno-sarmento.com.access.log;
    error_log /var/log/nginx/nuno-sarmento.com.error.log;
    
    # WordPress security and optimization rules
    include common/wordpress.conf;

    # Max upload size
    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

 

Step 12: Configure HTTP Security Headers

“HTTP security headers are a fundamental part of website security. Upon implementation, they protect you against the types of attacks that your site is most likely to come across. These headers protect against XSS, code injection, clickjacking, etc.”

Edit the main Nginx configuration:

sudo nano /etc/nginx/nginx.conf

Add these headers inside the http {} block:

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

# Hide Nginx version
server_tokens off;

# Additional security
more_clear_headers Server;

For the more_clear_headers directive to work, install the headers-more module:

sudo apt install libnginx-mod-http-headers-more-filter -y

Reload Nginx:

sudo systemctl reload nginx

You can check your websites headers security on the link below:
https://securityheaders.com/

 

Step 13: Install Let’s Encrypt SSL Certificate

On Ubuntu 24.04, Certbot is available directly from the Ubuntu repositories:

sudo apt install certbot python3-certbot-nginx -y

Obtain and install SSL certificate (replace with your domain):

sudo certbot --nginx -d nuno-sarmento.com -d www.nuno-sarmento.com

LetsEncrypt steps:

1 – Enter email address (used for urgent renewal and security notices): hello@nuno-sarmento.com

2 – Please read the Terms of Service. You must agree in order to register with the ACME server.
(A)gree/(C)ancel: A

3 – Would you be willing to share your email address with the Electronic Frontier Foundation?
(Y)es/(N)o: Y

4 – Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
1: No redirect
2: Redirect – Make all requests redirect to secure HTTPS access.
Select the appropriate number [1-2] then [enter]: 2

Test automatic renewal:

sudo certbot renew --dry-run

To manually renew SSL certificates:

sudo certbot renew

Test your SSL configuration:
https://www.ssllabs.com/ssltest/

 

Step 14: Install and Configure Fail2Ban

Fail2Ban protects your server from brute force attacks by banning IPs that show malicious signs.

sudo apt install fail2ban -y

Create a local configuration file:

sudo nano /etc/fail2ban/jail.local

Add the following configuration:


[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = hello@nuno-sarmento.com
sendername = Fail2Ban
action = %(action_)s
bantime.increment = true
bantime.factor = 24
bantime.maxtime = 4w

[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
action = %(action_)s


[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/*error.log

[nginx-noscript]
enabled = false
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/*access.log
maxretry = 6

[nginx-badbots]
enabled = false
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/*access.log
maxretry = 2

[nginx-noproxy]
enabled = false
port = http,https
filter = nginx-noproxy
logpath = /var/log/nginx/*access.log
maxretry = 2

[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/*error.log

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



Create WordPress-specific filter:

sudo nano /etc/fail2ban/filter.d/wordpress-hard.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login.php
            ^<HOST> .* "POST /xmlrpc.php
ignoreregex =

Enable and start Fail2Ban:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Check Fail2Ban status:

sudo fail2ban-client status
sudo fail2ban-client restart
sudo fail2ban-client status wordpress-hard

 

Step 15: Install WP-CLI and Setup System Cron

Replace WordPress built-in cron (wp-cron.php) with system cron for better performance.

Disable WordPress cron in wp-config.php:

sudo nano /var/www/nuno-sarmento.com/wp-config.php

Add before “That’s all, stop editing!”:

define( 'DISABLE_WP_CRON', true );

Create a system cron job:

sudo crontab -e -u www-data

Add this line (runs every 5 minutes):

*/5 * * * * /usr/local/bin/wp cron event run --due-now --path=/var/www/nuno-sarmento.com > /dev/null 2>&1

 

Step 16: Install Redis Object Cache (Optional)

Redis dramatically improves WordPress performance by caching database queries.

sudo apt install redis-server php8.3-redis -y

Enable and start Redis:

sudo systemctl enable redis-server
sudo systemctl start redis-server

Configure Redis for security:

sudo nano /etc/redis/redis.conf

Find and modify these settings:

bind 127.0.0.1 ::1
maxmemory 256mb
maxmemory-policy allkeys-lru

Restart Redis and PHP-FPM:

sudo systemctl restart redis-server
sudo systemctl restart php8.3-fpm

Install Redis Object Cache plugin via WP-CLI:

sudo -u www-data wp plugin install redis-cache --activate --path=/var/www/nuno-sarmento.com
sudo -u www-data wp redis enable --path=/var/www/nuno-sarmento.com

 

Step 17: Setup Automated Backups

Create automated database backups using a bash script.

sudo mkdir -p /var/backups/wordpress
sudo nano /usr/local/bin/wordpress-backup.sh
#!/bin/bash

DB_NAME="nunosarmento"
DB_USER="nunosarmentouser"
DB_PASS="YourSecurePassword123!"
BACKUP_DIR="/var/backups/wordpress"
SITE_DIR="/var/www/nuno-sarmento.com"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/db_backup_$DATE.sql.gz

tar -czf $BACKUP_DIR/files_backup_$DATE.tar.gz $SITE_DIR

find $BACKUP_DIR -type f -name "*.gz" -mtime +7 -delete

echo "Backup completed: $DATE"

Make the script executable:

sudo chmod +x /usr/local/bin/wordpress-backup.sh

Create a cron job for daily backups at 2 AM:

sudo crontab -e
0 2 * * * /usr/local/bin/wordpress-backup.sh >> /var/log/wordpress-backup.log 2>&1

 

Troubleshooting – Reported Issues

Error Nginx: 413 – Request Entity Too Large Error and Solution

The error “413 – Request Entity Too Large” indicates that web server configured to restrict large file size. To fix this edit your nginx.conf:

sudo nano /etc/nginx/nginx.conf

Add inside the http {} block:

# Set client body size to 100M
client_max_body_size 100M;

Reload Nginx:

sudo systemctl reload nginx

502 Bad Gateway Error

Usually indicates PHP-FPM is not running:

sudo systemctl status php8.3-fpm
sudo systemctl restart php8.3-fpm
sudo tail -f /var/log/nginx/error.log

Database Connection Error

Verify database credentials in wp-config.php:

mysql -u nunosarmentouser -p nunosarmento

 

Useful Commands Reference

Service Management:

# Restart services
sudo systemctl restart nginx
sudo systemctl restart php8.3-fpm
sudo systemctl restart mariadb
sudo systemctl restart redis-server

# Check service status
sudo systemctl status nginx
sudo systemctl status php8.3-fpm
sudo systemctl status mariadb

# View logs
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/nginx/nuno-sarmento.com.error.log

WP-CLI Common Commands:

# Update WordPress core
sudo -u www-data wp core update --path=/var/www/nuno-sarmento.com

# Update all plugins
sudo -u www-data wp plugin update --all --path=/var/www/nuno-sarmento.com

# List plugins
sudo -u www-data wp plugin list --path=/var/www/nuno-sarmento.com

# Database optimization
sudo -u www-data wp db optimize --path=/var/www/nuno-sarmento.com

# Search and replace (useful for domain changes)
sudo -u www-data wp search-replace 'old-domain.com' 'new-domain.com' --path=/var/www/nuno-sarmento.com

 

Post-Installation Security Checklist

✅ UFW firewall enabled with only necessary ports open
✅ Fail2Ban installed and configured
✅ SSL certificate installed with A+ rating
✅ Security headers configured
✅ WordPress security constants set in wp-config.php
✅ File permissions hardened
✅ SSH root login disabled (recommended)
✅ Database user has minimal required privileges
✅ PHP dangerous functions disabled
✅ WordPress auto-updates enabled
✅ Regular backups scheduled
✅ System cron replacing wp-cron.php
✅ Redis cache installed (optional)
✅ Security plugins installed (recommended)
✅ XML-RPC disabled

 

Congratulations! You now have a secure, optimized, and production-ready WordPress installation on Ubuntu 24.04 with Nginx, PHP 8.3, MariaDB, and comprehensive security hardening.

ABOUT AUTHOR

Nuno

Hi, I'm a Freelance Web Developer and WordPress Expert based in London with a wealth of website development and support experience. I am great at problem solving and developing quick solutions.