WordPress on a VPS: When to Move, Which Stack, What to Cache

When WordPress on a VPS beats shared hosting, which stack to install, how page, object and OPcache caching help, and how to size PHP-FPM workers.

Short answer: move WordPress to a VPS when shared hosting limits start to hold your site back: busy WooCommerce or membership pages, several sites, or the need for server-level caching, Redis or a real cron. A sensible stack is Nginx or Apache with PHP-FPM on PHP 8.3 or newer, MariaDB 10.11 or MySQL 8.0 or newer, and HTTPS. Then cache in layers: OPcache for PHP, a page cache for visitors, an object cache for the database and browser caching for static files. If you do not want to look after a server yourself, stay on good shared hosting or choose a managed VPS.

Terminal showing curl measuring time to first byte of a WordPress site
Measuring time to first byte of a WordPress site (our blog) with curl, three runs.

When a VPS beats shared hosting for WordPress

WordPress’s own optimisation guide puts it simply: on shared hosting you have very little control over server settings, while on a VPS or dedicated server you control the whole software stack. That control pays off when:

  • You keep hitting account limits. Shared hosting sets per-account limits. On CloudLinux servers, for example, an account that reaches its limit of concurrent connections gets an error 508 page (Resource Limit Reached), according to the CloudLinux documentation. Slow checkouts or 508 errors at busy times are the usual sign.
  • Many visitors are logged in. Shops, membership sites and courses show personal pages that a page cache cannot serve, so every request runs PHP and queries the database.
  • You want server-level tools. Nginx page caching, a Redis or Valkey object cache, WP-CLI and a real cron job all need a server you control.
  • You run several sites. One VPS can host several WordPress sites, each with its own database, PHP pool and certificate.

A VPS is the wrong move if nobody will apply operating system updates, watch disk space or restore a backup. For a small brochure site, good shared hosting is usually simpler to run and cheaper. Our post Shared vs VPS Hosting: When to Upgrade covers the general case.

The stack: what to install

The recommendations come from the WordPress requirements page. The latest release when we checked was WordPress 7.1.2.

LayerChoiceNotes
Web serverNginx or ApacheWordPress recommends either. Nginx needs a try_files rule for pretty permalinks; Apache uses .htaccess.
PHPPHP 8.3 or newer, run as PHP-FPMphp.net lists security support for 8.3 until the end of 2027; 8.4 and 8.5 run longer.
DatabaseMariaDB 10.11 or newer, or MySQL 8.0 or newerKeep it on the same VPS unless the site is very busy.
HTTPSA free Let’s Encrypt certificateWordPress lists HTTPS support as a requirement.
ToolsWP-CLIUpdates, backups and search and replace from the command line.

Cache in layers

WordPress’s caching guide calls caching the fastest way to improve performance. Each layer does a different job:

  • OPcache, according to the PHP manual, stores precompiled script bytecode in shared memory, so PHP does not load and parse every script on each request. Check it with your PHP-FPM binary, for example php-fpm8.5 -i | grep '^opcache.enable ='; on our Ubuntu 26.04 test system it printed opcache.enable => On => On.
  • A page cache stores the finished HTML of a page and serves it without running WordPress. A plugin can do this, or Nginx can do it with fastcgi_cache.
  • An object cache keeps the results of database queries in memory between requests. It helps most where a page cache cannot: logged-in users, shops and the admin area. See how to speed up WordPress with Redis or Valkey.
  • Browser caching tells visitors’ browsers to keep images, CSS and JavaScript, so repeat visits download less.

What a page cache did in our test

We built a fresh WordPress 7.1.2 site in Docker on a Mac, with PHP 8.4.26 (PHP-FPM with its default pool of 5 workers), MariaDB 11.8 and Nginx, then sent 1,000 requests for the home page, 10 at a time, with ApacheBench:

SetupRequests per secondSingle request (warm)
No page cache (PHP and database every time)215about 21 ms
Nginx fastcgi_cache hitabout 25,000under 1 ms
An example from one test machine, not a benchmark of any hosting plan. A real site with a theme and plugins is slower without a cache.

The point is the ratio, not the numbers: a cached page costs the server almost nothing. The core of the Nginx setup we used is short. The Nginx FastCGI module documentation explains each directive:

# in the http block
fastcgi_cache_path /var/cache/nginx/wp levels=1:2 keys_zone=WP:10m inactive=60m;

# in the server block
set $skip_cache 0;
if ($http_cookie ~* "wordpress_logged_in|comment_author|wp-postpass") { set $skip_cache 1; }

# in location ~ \.php$
fastcgi_cache WP;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache $upstream_cache_status;

The cookie rule keeps logged-in users and commenters away from cached pages, and Nginx only caches GET and HEAD requests by default, so form posts are never cached. The X-Cache header lets you check the result with curl -sI https://example.com/ | grep -i x-cache. In our test (Nginx 1.30.5) the first request showed MISS, the next ones HIT, and a request with a wordpress_logged_in cookie BYPASS. After you publish or update content, the cached copy stays until it expires (10 minutes here), so use a plugin that clears it or keep the time short.

Size PHP-FPM to your memory

Uncached requests are handled by PHP-FPM workers. According to the PHP manual, pm.max_children sets the limit on the number of simultaneous requests PHP will serve. Too low and visitors queue; too high and the server runs out of memory. Measure your workers after the site has been busy for a while:

ps -eo rss,cmd | awk '/[p]hp-fpm: pool/ {s+=$1; n++} END {printf "%d workers, average %.0f MB\n", n, s/n/1024}'

On our fresh test site it printed 3 workers, average 49 MB. About 22 MB of each was shared memory, such as OPcache, which is counted in every worker but exists only once, so the figure is a safe upper estimate. Plugins, page builders and WooCommerce raise it. Then divide: if you can spare 2 GB for PHP after the operating system, database and cache, and workers average 80 MB, about 25 workers fit. Set pm.max_children near that and watch memory at your busiest time.

Swap WP-Cron for a real cron job

WordPress normally runs scheduled tasks when someone visits the site. The WordPress documentation suggests calling wp-cron.php from the system scheduler instead and turning off the per-visit check with this line in wp-config.php:

define( 'DISABLE_WP_CRON', true );

Then add a crontab entry such as */15 * * * * wget -q --delete-after https://example.com/wp-cron.php. Our guide to disabling wp-cron.php has more detail.

Before you move

WordPress on a Ucartz VPS

Our KVM VPS plans have NVMe SSD storage, full root access and unmetered bandwidth, and deployment is automated once your order is approved. Install Nginx or Apache, PHP, MariaDB and WordPress yourself, or ask our team to set them up; work such as moving an existing site or web server tuning is billed by time, from $8 per 15 minutes. Free Basic Managed Support covers quick tasks, and self-managed VPS are not backed up by us, so keep your own off-site copies. If you would rather we handled updates, security and monitoring, look at our managed cPanel VPS instead.

Vishnu
Vishnu