WordPress is often associated with managed hosting, but a small VPS is enough for a single site if you are comfortable administering Linux. This guide deploys WordPress directly on Ubuntu 26.04 LTS with Nginx, PHP-FPM and MariaDB.
This is intentionally a single-application deployment. There is no Docker, Kubernetes or hosting control panel involved. You get a small and understandable stack that you can inspect, update and back up yourself.
What are we deploying?
At the time of writing, the latest stable release available from WordPress.org is WordPress 7.0.4.
| Component | Version / choice |
|---|---|
| Operating system | Ubuntu 26.04 LTS "Resolute Raccoon" |
| Web server | Nginx 1.28.x |
| Application runtime | PHP 8.5 with PHP-FPM |
| Database | MariaDB 11.8.x |
| CMS | WordPress 7.0.4 |
| HTTPS | Let's Encrypt with Certbot |
WordPress currently recommends PHP 8.3 or newer, MariaDB 10.11 or newer (or MySQL 8.0 or newer), and HTTPS. Ubuntu 26.04's default packages meet those requirements.
Before you start
You need:
- A fresh Ubuntu 26.04 LTS VPS with at least 1 GB of RAM for a small site.
- A non-root user with
sudoaccess. - A domain pointing to the VPS public IP.
- SSH access to the server.
- A strong, unique password for the WordPress database user.
Replace example.com and all placeholder passwords in the commands below with your own values.
Step 1: Update Ubuntu
Connect over SSH and update the base system:
sudo apt update && sudo apt upgrade -ybashIf the kernel was upgraded, reboot before continuing:
sudo rebootbashStep 2: Install the stack
Install Nginx, MariaDB, PHP-FPM and the extensions WordPress commonly needs:
sudo apt install -y nginx mariadb-server mariadb-client \
php-fpm php-cli php-mysql php-curl php-gd php-intl \
php-mbstring php-xml php-zip php-imagick unzip curlbashCheck the installed versions and services:
nginx -v
php -v
mariadb -V
systemctl --no-pager --type=service --state=running | grep -E 'nginx|mariadb|php.*fpm'bashUbuntu 26.04 uses the versioned PHP-FPM socket below:
/run/php/php8.5-fpm.socktextCheck the actual socket if you installed a different PHP branch:
ls /run/php/bashStep 3: Secure MariaDB and create the database
Run the security helper:
sudo mariadb-secure-installationbashOn a fresh Ubuntu installation, MariaDB's root account normally uses Unix socket authentication. You can therefore open the administrative shell with:
sudo mariadbbashCreate a dedicated database and user. Do not use the MariaDB root account from WordPress:
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'replace-with-a-long-random-password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
FLUSH PRIVILEGES;
EXIT;sqlThe database remains bound to localhost. A single-site VPS has no reason to expose MariaDB on the public internet.
Step 4: Create the web root
Use a dedicated directory for this site:
sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.combashThe Nginx and PHP-FPM processes will serve the site as www-data.
Step 5: Download WordPress
Download the current stable archive from the official WordPress endpoint:
cd /tmp
curl -fLO https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
sudo cp -a wordpress/. /var/www/example.com/public/
sudo chown -R www-data:www-data /var/www/example.combashVerify that the expected files are present:
sudo ls -la /var/www/example.com/publicbashThe latest.tar.gz URL follows the current stable WordPress release. If you need reproducible deployments, download a versioned archive and record its checksum instead of relying on latest.
Step 6: Configure wp-config.php
Create the configuration file from the example:
cd /var/www/example.com/public
sudo -u www-data cp wp-config-sample.php wp-config.php
sudo -u www-data nano wp-config.phpbashSet the database values:
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wordpress' );
define( 'DB_PASSWORD', 'replace-with-a-long-random-password' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );phpGenerate fresh salts from the official WordPress API:
curl -fsSL https://api.wordpress.org/secret-key/1.1/salt/bashReplace the placeholder salt definitions in wp-config.php with the output. Never commit this file or publish its contents.
define('AUTH_KEY', 'the-random-value-given-by-the-api');
define('SECURE_AUTH_KEY', 'the-random-value-given-by-the-api');
define('LOGGED_IN_KEY', 'the-random-value-given-by-the-api');
define('NONCE_KEY', 'the-random-value-given-by-the-api');
define('AUTH_SALT', 'the-random-value-given-by-the-api');
define('SECURE_AUTH_SALT', 'the-random-value-given-by-the-api');
define('LOGGED_IN_SALT', 'the-random-value-given-by-the-api');
define('NONCE_SALT', 'the-random-value-given-by-the-api');phpAdd these settings before the final comment in wp-config.php:
define( 'DISALLOW_FILE_EDIT', true );
define( 'WP_AUTO_UPDATE_CORE', 'minor' );phpDISALLOW_FILE_EDIT removes the theme and plugin editor from the administration panel. Core minor updates remain enabled while major updates can be tested before applying them.
Lock down the configuration file itself:
sudo chmod 640 /var/www/example.com/public/wp-config.phpbashStep 7: Configure the Nginx server block
Create a dedicated virtual host:
sudo nano /etc/nginx/sites-available/example.combashUse this configuration. The try_files fallback is what makes WordPress pretty permalinks work:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}
# PHP should never execute from the media upload directory.
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
# Do not expose hidden files, except ACME challenge files.
location ~ /\.(?!well-known).* {
deny all;
}
}nginxEnable the site and remove the default Nginx page:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxbashCheck the response before continuing:
curl -I http://example.combashYou should receive an HTTP response from Nginx. The WordPress installer will be available once DNS resolves to the VPS.
Step 8: Adjust PHP limits
The Nginx upload limit is 64M, so PHP should allow at least the same value. Edit the FPM configuration:
sudo nano /etc/php/8.5/fpm/php.inibashSet or update these values:
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120iniRestart PHP-FPM after saving:
sudo systemctl restart php8.5-fpmbashStep 9: Configure the firewall
Allow SSH and web traffic before enabling UFW:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbosebashDo not open port 3306. MariaDB should remain reachable only from localhost.
Step 10: Enable HTTPS
Install Certbot and its Nginx integration:
sudo apt install -y certbot python3-certbot-nginxbashRequest a certificate for both hostnames:
sudo certbot --nginx -d example.com -d www.example.combashChoose the HTTP-to-HTTPS redirect when prompted. Verify automatic renewal:
sudo certbot renew --dry-runbashHTTPS is not an optional finishing touch for WordPress. It protects login credentials, administration sessions and site visitors.
Step 11: Finish the WordPress installation
Open the HTTPS URL in a browser:
https://example.comtextComplete the installer with:
- Site title.
- Administrator username that is not
admin. - A unique, long administrator password.
- An administrator email address that you monitor.
- Search engine visibility according to whether the site is ready for publication.
After logging in, visit Settings → Permalinks and save the preferred structure. The Nginx try_files rule already supports the resulting URLs.
Verify the deployment
Check the important services:
sudo systemctl is-active nginx
sudo systemctl is-active mariadb
sudo systemctl is-active php8.5-fpmbashCheck the public response and HTTPS redirect:
curl -I http://example.com
curl -I https://example.combashThen verify from the WordPress dashboard that:
- The site URL uses
https://. - Media uploads work within the configured limit.
- Pretty permalinks work.
- The PHP version and database connection are healthy.
- No unexpected plugin or theme editor is available.
Backups and maintenance
A VPS is not a backup. At minimum, back up the database and wp-content to storage outside the server:
sudo mariadb-dump wordpress | gzip > wordpress-$(date +%F).sql.gz
sudo tar -czf wp-content-$(date +%F).tar.gz \
-C /var/www/example.com/public wp-contentbashTest that you can restore both backups. Keep multiple generations and never store the only copy on the VPS.
For ongoing maintenance:
- Apply Ubuntu security updates regularly.
- Keep WordPress, themes and plugins updated.
- Remove unused plugins and themes.
- Monitor disk space, PHP-FPM logs and Nginx logs.
- Review administrator accounts and application passwords.
- Test backups and certificate renewal periodically.
Troubleshooting
Nginx returns 502 Bad Gateway
PHP-FPM is probably stopped or the socket path does not match the installed PHP version:
sudo systemctl status php8.5-fpm
ls -l /run/php/
sudo nginx -tbashPermalinks return 404
Confirm that the server block contains:
try_files $uri $uri/ /index.php?$args;nginxThen save the permalink settings again in WordPress and reload Nginx if the configuration changed.
Uploads fail with 413 Request Entity Too Large
Increase client_max_body_size in Nginx and make sure post_max_size is at least as large as upload_max_filesize in PHP. Restart PHP-FPM and reload Nginx after changing them.
WordPress asks for FTP credentials
Check that the WordPress files are owned by the account running PHP-FPM:
sudo chown -R www-data:www-data /var/www/example.combashDo not solve this by making the whole web root world-writable.
References
- WordPress download
- WordPress requirements
- WordPress installation documentation
- Ubuntu 26.04 LTS release notes
- Nginx beginner's guide
- MariaDB documentation
- Certbot Nginx instructions
AI-generated image disclosure
The cover image (/images/posts/wordpress-nginx-header.webp) was generated with the assistance of an AI model (deepseek-v4-flash). In accordance with Regulation (EU) 2024/1689 (EU Artificial Intelligence Act, Article 50), this content is disclosed as AI-generated. It is an original illustration created for this article without third-party images, logos or external assets. WordPress is a trademark of the WordPress Foundation; the name appears descriptively to identify the software discussed in this article. No affiliation or endorsement is implied.
