Shared hosting runs WordPress fine until it does not. The moment traffic picks up, or you install a few heavier plugins, or you need server-level control that the hosting panel does not expose, you hit the ceiling. Moving WordPress to a VPS gives you dedicated resources and full authority over the stack. The catch is that a VPS does not come preconfigured for WordPress. You are building the environment yourself.
Most guides for this stop at "install WordPress and visit the URL." That skips the parts that actually determine whether your site loads in under two seconds or crawls along at five. PHP-FPM pool sizing, database tuning, object caching, correct file permissions: these are the decisions that separate a properly hosted WordPress site from one running on a VPS with shared-hosting performance.
This guide covers a full LEMP stack setup (Linux, Nginx, MariaDB, PHP-FPM) tuned for WordPress. It assumes you already have a VPS provisioned with a Linux distribution, have SSH access, and have completed the baseline server hardening. If you are starting from scratch, work through the first-time VPS setup guide first, then come back here.
Why LEMP Instead of LAMP
The traditional WordPress stack uses Apache. It works. Plenty of WordPress sites run on Apache without issues. But Nginx handles static files and concurrent connections more efficiently with less memory overhead. For a VPS where you are managing a fixed pool of RAM, that efficiency gap matters.
Nginx does not process .htaccess files, which means some WordPress plugins that rely on Apache rewrite rules need their Nginx equivalents configured in the server block. That is a one-time setup cost, not something you revisit regularly. The performance return over the life of the site is worth the extra ten minutes.
MariaDB over MySQL is a straightforward choice for WordPress. It is a drop-in replacement, uses the same client libraries, and the WordPress database abstraction layer treats them identically. Most Linux distributions ship MariaDB in their default repositories.
Install the Stack
Update your packages first, then install Nginx, MariaDB, and PHP-FPM with the extensions WordPress needs.
On Debian or Ubuntu:
sudo apt update
sudo apt install nginx mariadb-server php-fpm php-mysql php-curl php-gd php-intl php-mbstring php-soap php-xml php-zip php-imagick -y
On Rocky Linux or AlmaLinux:
sudo dnf install nginx mariadb-server php-fpm php-mysqlnd php-curl php-gd php-intl php-mbstring php-soap php-xml php-zip php-pecl-imagick -y
Enable and start all three services:
sudo systemctl enable --now nginx mariadb php-fpm
Verify each is running:
sudo systemctl status nginx mariadb php-fpm
If your firewall is active (it should be), confirm that HTTP and HTTPS are allowed. The security checklist covers firewall configuration in detail.
Secure MariaDB
Run the security script immediately after installation:
sudo mysql_secure_installation
This walks through setting a root password, removing anonymous users, disabling remote root login, and dropping the test database. Answer yes to all of them. There is no reason to keep any of those defaults on a production server.
Create the WordPress Database
Log into MariaDB and create a dedicated database and user. Do not run WordPress as the MariaDB root user.
sudo mariadb
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
The utf8mb4 character set is non-negotiable. It supports the full Unicode range, including emoji, which WordPress content will inevitably contain. The older utf8 encoding (actually a three-byte subset) silently corrupts four-byte characters. Using it in a new installation is a mistake you will discover months later when a user's review contains an emoji and the text after it vanishes.
Replace YOUR_STRONG_PASSWORD with something generated by a password manager. This password goes into wp-config.php and nowhere else.
Configure PHP-FPM for WordPress
PHP-FPM is where VPS WordPress setups either perform well or waste resources. The default pool configuration targets a general-purpose PHP environment, not a WordPress site with known memory characteristics.
Find your pool configuration file. On Debian/Ubuntu, look at /etc/php/{version}/fpm/pool.d/www.conf. On RHEL-based systems, check /etc/php-fpm.d/www.conf.
Key settings to adjust:
; Unix socket for same-host connections (faster than TCP)
listen = /run/php/php-fpm.sock
; Run the pool as the web server user
user = www-data
group = www-data
; Fixed worker count for predictable memory usage
pm = static
pm.max_children = 4
Why static instead of dynamic or ondemand? On a VPS with a fixed amount of RAM, predictable memory usage matters more than adaptive scaling. The dynamic and ondemand modes spin workers up and down, which introduces latency when a request hits a cold pool and makes memory consumption harder to forecast. With static, every request gets a warm process and you know exactly how much RAM the pool consumes.
Sizing the worker count: Each PHP-FPM worker handling WordPress uses roughly 40 to 60 MB of RAM under typical load. On a 2 GB VPS, subtract what the OS, Nginx, and MariaDB require (roughly 600 to 800 MB combined), and you have room for about 4 to 6 workers. Start at 4. Monitor with htop or free -h under real traffic and adjust from there. If the server begins swapping to disk, you have too many workers.
Restart PHP-FPM after editing:
sudo systemctl restart php-fpm
Download and Install WordPress
Use WP-CLI for installation. It is faster and more predictable than uploading files through a browser.
Install WP-CLI:
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
Create the web root and download WordPress:
sudo mkdir -p /var/www/yourdomain.com
cd /var/www/yourdomain.com
sudo wp core download --allow-root
Generate the configuration file:
sudo wp config create \
--dbname=wordpress \
--dbuser=wp_user \
--dbpass=YOUR_STRONG_PASSWORD \
--dbhost=localhost \
--dbcharset=utf8mb4 \
--allow-root
Set file ownership and permissions so the web server can read everything and WordPress can write only where it needs to:
sudo chown -R www-data:www-data /var/www/yourdomain.com
sudo find /var/www/yourdomain.com -type d -exec chmod 750 {} \;
sudo find /var/www/yourdomain.com -type f -exec chmod 640 {} \;
The directories get 750 (owner reads, writes, and executes; group reads and executes; others have no access). Files get 640 (owner reads and writes; group reads; others have no access). Do not set 777 on anything. A WordPress installation with world-writable files is an open invitation to every automated exploit scanner on the internet.
Configure Nginx for WordPress
Create a server block that handles WordPress permalinks, routes PHP to FPM, and blocks access to sensitive files.
Create /etc/nginx/sites-available/yourdomain.com (or the equivalent path for your distribution):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com;
index index.php index.html;
# WordPress permalink support
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP requests to FPM
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Block sensitive files
location ~ /\.(ht|git|svn) {
deny all;
}
location = /wp-config.php {
deny all;
}
# Browser caching for static assets
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Enable the site and test:
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Fix any errors nginx -t reports before reloading. The most common problems are a typo in the socket path or a missing fastcgi_params include file (the exact filename and path vary between distributions; check /etc/nginx/ for the one your system ships).
SSL Before the Install, Not After
Set up HTTPS before running the WordPress installer. WordPress stores the site URL in the database during installation, and changing it after the fact requires a search-and-replace operation across serialized PHP data. Getting this wrong corrupts your site.
The full SSL guide covers Certbot in depth. The short version for Nginx:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot modifies your Nginx configuration to enable TLS and set up an HTTP-to-HTTPS redirect. Confirm the padlock appears at https://yourdomain.com before proceeding.
Run the WordPress Installer
Open https://yourdomain.com in your browser. WordPress presents its setup screen. Choose a language, enter a site title, create an admin account, and finish.
Three decisions matter here:
- Admin username: Do not use "admin." Automated brute-force attacks target that username specifically. Pick something that is not guessable from the site's public content.
- Password: Accept the strong generated password WordPress offers. Overriding it with something shorter or more memorable weakens the only defense your admin panel has besides a login URL.
- Search engine visibility: Leave "discourage search engines" unchecked unless this is a staging environment. Control indexing more precisely later through a robots.txt file or an SEO plugin.
Add Object Caching
WordPress queries the database on every page load by default. Object caching stores the results of repeated queries in memory so they do not run again until the underlying data changes. On a VPS where database performance is bounded by your allocated resources, this measurably reduces page generation time.
Redis is the standard choice:
sudo apt install redis-server -y
sudo systemctl enable --now redis-server
Install a Redis object cache plugin through the WordPress admin panel or via WP-CLI. Several well-maintained options exist in the plugin directory. After activation, confirm the connection is live by checking the plugin's status page.
Redis for a typical WordPress site consumes 10 to 30 MB of RAM. On a VPS with 2 GB or more, that is a negligible cost for a noticeable latency improvement on every dynamic page load.
Performance Decisions Worth Getting Right
Beyond the stack configuration, a handful of choices disproportionately affect how fast WordPress feels for visitors.
Page Caching
A page caching layer serves fully rendered HTML to returning visitors without invoking PHP or the database. For a content-heavy site where most visitors see the same pages, this is the single largest performance gain available.
You have two approaches:
| Approach | Where it runs | Pros | Cons |
|---|---|---|---|
| PHP caching plugin | Inside WordPress | Easy to install, no Nginx config changes | Still starts a PHP process for every request |
| FastCGI cache | At the Nginx level | Serves from disk before PHP is invoked, fastest option | Requires Nginx configuration, needs cache purge logic |
Pick one, not both. Running a PHP-level cache plugin alongside Nginx FastCGI cache creates conflicts, serves stale content, and is harder to debug than either approach alone.
Image Handling
Images are typically the heaviest assets on a WordPress page. Serve them in WebP format where possible, with appropriate compression. Multiple plugins handle automatic conversion and markup rewriting. Set explicit width and height attributes on every <img> tag to prevent layout shifts during loading, and use loading="lazy" for images below the fold.
Plugin Discipline
Every active plugin adds PHP execution time. A WordPress installation with 30 plugins will be slower than one with 10, regardless of how well the server is tuned. Load only what the site genuinely requires. Audit the plugin list periodically and deactivate anything unused. The performance cost of a single poorly written plugin can outweigh every other optimization on this page.
Database Maintenance
WordPress accumulates post revisions, transient options, trashed items, and orphaned metadata over months and years of operation. A scheduled cleanup (via a maintenance plugin or a cron job running WP-CLI commands like wp transient delete --all) keeps the database lean and queries fast.
Managed WordPress Hosting: the Alternative Path
Everything above describes the unmanaged approach: you build the stack, you maintain it, you own every configuration decision. This is the right path if you want full control and are comfortable with Linux server administration.
If the operational burden sounds like more than you want to carry, a managed VPS shifts much of it to the provider. Some hosts offer WordPress-specific managed plans where the web server, PHP, caching, and automatic updates come preconfigured. You still control your WordPress installation, but the server environment underneath is somebody else's job.
The trade-off is cost versus time. Managed plans charge more per unit of compute because they bundle the operational work into the price. For a business site where every hour spent on server maintenance is an hour not spent on the business itself, the premium is often justified. For a developer who prefers the infrastructure side, unmanaged is both cheaper and more satisfying.
Browse the providers directory filtered by hosting type to see which VPS hosts offer managed plans. The community reviews are useful here too: filter by WordPress-related feedback and pay attention to the support and reliability ratings, since those two categories reveal the most about whether a provider's managed tier delivers on its promises.
Mistakes That Come Back Later
These patterns come up repeatedly in WordPress VPS setups and cause problems weeks or months after the initial installation.
- Running WordPress as root. The web server process should run as a dedicated user (
www-dataon most distributions). Running it as root means a vulnerability in any plugin grants an attacker full system access. - Loose file permissions. WordPress needs write access to
wp-content/uploadsand occasionally towp-content/for updates. It does not need write access to core files, theme PHP files, orwp-config.php. Restrict permissions to exactly what is required. - No automated backups. A VPS does not back itself up. Set up automated database dumps (a cron job running
wp db exportto a dated file) and file-level backups to an offsite location. Test the restore process before you need it. - Forgetting PHP and OS updates. The WordPress dashboard nags about core and plugin updates. PHP version upgrades and OS security patches do not nag. Schedule regular maintenance windows to apply them, or configure automatic security updates with
unattended-upgradeson Debian/Ubuntu. - Stacking redundant caching layers. A PHP page caching plugin and Nginx FastCGI cache running simultaneously will conflict. Use one approach or the other.
Moving Forward
A VPS running WordPress on a properly configured LEMP stack with object caching and locked-down file permissions is a solid, fast foundation. Where you go from here depends on the kind of site you are building and how much traffic you expect.
If you are coming from shared hosting, the biggest adjustment is ownership. You now control everything the hosting panel used to hide from you. That means more flexibility, but also more responsibility. Both are real.
The OS selection guide covers the distribution decision in depth if you have not settled on one yet. And for anyone still choosing a provider, the VPS providers directory is the starting point: filter by hosting type, read the user reviews, and pay particular attention to reliability and support scores. Those two categories matter most when the WordPress site is the revenue-generating asset and not a weekend experiment.