Install Memcached on a VPS: Setup, Sessions & Fixes
If you're running a couple of PHP apps on one VPS - maybe a Laravel API, a legacy CodeIgniter app, and a WordPress install sharing the same box - you've probably hit the point where PHP's default file-based sessions start creaking under load. Memcached is the classic fix, but it trips people up in two very specific ways: the PHP extension naming is a mess, and by default it's happy to sit there listening on a port with zero authentication. Here's how to install it properly, wire it into PHP, and avoid both traps.
Symptom: Slow Apps Under Concurrent Load, or a "Class Memcached Not Found" Crash
There are two very different situations that lead people here:
- Session lock contention. A user opens your app in two tabs, or your frontend fires several AJAX calls at once. PHP's default file-based session handler locks the session file for the duration of each request, so those requests queue up and run one after another instead of in parallel - even though nothing else on the server is busy.
- An app that explicitly requires Memcached. Some legacy apps, some Laravel configs, and a fair number of self-hosted tools (Magento, older Symfony projects) list Memcached as a cache or session driver in their config, and just fail outright -
Class "Memcached" not found- if the extension isn't installed.
Both point to the same fix, so let's set it up correctly the first time.
Cause: No Shared, In-Memory Cache Between PHP Workers
PHP-FPM runs a pool of worker processes. Each one is a separate process with its own memory space - there's no built-in way for worker #3 to see data that worker #1 just computed. Two things suffer from this:
- Sessions. The default
session.save_handler = fileswrites each user's session to a flat file under/var/lib/php/sessions. Concurrent requests from the same session have to wait for a file lock to release, which is fine at low traffic and painfully slow once a user's browser fires off parallel requests. - Object/data caching. Without a shared cache, every worker independently re-runs the same expensive lookup - a pricing calculation, a third-party API call, a heavy SQL query - because it has no way of knowing another worker already did it thirty seconds ago.
Memcached solves both by sitting outside the PHP process pool as its own daemon, holding data in RAM that any worker (or any app on the box) can read and write.
Fix: Install and Wire Up Memcached
1. Install the Memcached Daemon
On Ubuntu/Debian:
sudo apt update
sudo apt install memcached libmemcached-tools -y
On AlmaLinux/RHEL:
sudo dnf install memcached libmemcached -y
sudo systemctl enable --now memcached
2. Bind It to Localhost Only
Memcached ships with no authentication in its default configuration. If it's reachable from the public internet, anyone can read, write, or flush your cache - and UDP-enabled instances have been abused for DDoS amplification attacks in the past. Edit the config:
sudo nano /etc/memcached.conf
Make sure these lines are set (Ubuntu/Debian path shown; on RHEL it's /etc/sysconfig/memcached):
# Listen only on localhost
-l 127.0.0.1
# Memory limit in MB - size this to what you can spare, not all your RAM
-m 128
# Disable UDP entirely (it's the vector abused for amplification attacks)
-U 0
Restart to apply:
sudo systemctl restart memcached
Then confirm it's only listening locally:
sudo ss -tlnp | grep memcached
# should show 127.0.0.1:11211, not 0.0.0.0:11211
3. Install the Right PHP Extension
This is where most setups go wrong. There are two unrelated PHP extensions with almost identical names:
| Extension | Backed by | Status | Use it? |
|---|---|---|---|
php-memcache | Custom protocol implementation | Unmaintained, abandoned since PHP 5.x era | No |
php-memcached | libmemcached | Actively maintained, supports igbinary, SASL | Yes |
Install the correct one, matched to whichever PHP version your app actually runs (check with php -v first if you have multiple versions installed via update-alternatives or Remi/Ondřej repos):
# Ubuntu/Debian, PHP 8.3 example
sudo apt install php8.3-memcached -y
# AlmaLinux/RHEL
sudo dnf install php-pecl-memcached -y
Restart PHP-FPM so the extension actually loads into the worker pool:
sudo systemctl restart php8.3-fpm
Verify it loaded:
php -m | grep -i memcached
If that's blank but apt list --installed | grep memcached shows it's installed, you almost certainly restarted the wrong PHP-FPM version, or your CLI PHP binary is a different version from the one Nginx/Apache hands requests to.
4. Point PHP Sessions at Memcached
To fix the session-locking problem specifically, edit your pool's php.ini or the site's FPM pool config:
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"
For a single app, dropping this into /etc/php/8.3/fpm/conf.d/99-memcached-sessions.ini works well. If you're running multiple sites on the box and only want one of them on Memcached sessions, set it in that site's own FPM pool file under [pool-name] instead of globally:
php_admin_value[session.save_handler] = memcached
php_admin_value[session.save_path] = "127.0.0.1:11211"
Restart PHP-FPM again after this change.
5. Confirm It's Actually Being Used
# Live stats - watch curr_items and get_hits climb as traffic hits the app
memcached-tool 127.0.0.1:11211 stats
# Or a quick raw check
printf "stats\r\n" | nc 127.0.0.1 11211 | head -20
If curr_connections stays flat at 1 and cmd_get/cmd_set never move while you're clicking around the app, PHP isn't actually talking to Memcached yet - double-check step 3 and 4 rather than assuming it's working.
Prevention
- Never expose port 11211 externally. Confirm your firewall (ufw, firewalld, or your provider's security group) blocks it by default, don't rely on the
-l 127.0.0.1bind alone - defense in depth matters here. - Size
-mdeliberately. Memcached will happily use exactly what you give it and evict the oldest data (LRU) once it's full. Giving it too much starves MySQL, PHP-FPM, and everything else sharing the VPS's RAM. - Treat it as disposable. Memcached keeps everything in RAM only - a restart, an OOM kill, or a reboot wipes it clean. Never store anything there that isn't safe to lose (sessions being logged out is annoying, not catastrophic; don't use it as your only copy of anything that matters).
- Watch eviction counts.
memcached-tool 127.0.0.1:11211 statsshows anevictionscounter. If it's climbing steadily, your-mvalue is too small for your working set and you're thrashing rather than caching.
Frequently asked questions
Memcached vs Redis for a VPS - which should I use?
Redis is the better default for most WordPress/app object-caching because it supports persistence, richer data types, and pub/sub. Memcached is simpler and slightly lighter for pure key-value caching and PHP sessions, and it's still what a number of legacy apps (older Magento, some Symfony configs) explicitly expect. If your app doesn't require one specifically, Redis is the safer general-purpose choice; use Memcached when an app calls for it or you just need dead-simple session storage.
Is it safe to expose Memcached beyond localhost?
Not without extra work. Memcached has no authentication by default, so anything that can reach port 11211 can read, write, or flush your cache. Bind it to 127.0.0.1 with the -l flag, disable UDP with -U 0, and block the port at the firewall. Only open it up if you're connecting from a separate app server, and in that case put it behind a private network or VPN, not the public internet.
My cached data disappeared after a server restart - why?
That's expected behavior, not a bug. Memcached stores everything in RAM only and has no disk persistence, so a restart, an OOM kill, or a reboot clears it completely. Never rely on it as the only copy of data you can't afford to lose - it's a cache, not a database.
Can I use Memcached for WordPress too, not just custom PHP apps?
Yes, via a plugin like W3 Total Cache or WP Rocket configured with the Memcached backend, or a lightweight persistent object cache drop-in. That said, most WordPress-focused setups get more out of Redis (see our Redis Object Cache guide) since it plays more nicely with WordPress's cache groups and supports igbinary compression well. Memcached still works fine if it's already installed for another app on the same VPS.
How much RAM should I give Memcached with the -m flag?
Start small - 64 to 128 MB is plenty for session storage and light caching on most small-to-mid VPS plans - then watch the evictions counter with memcached-tool. If evictions climb steadily under normal traffic, raise -m in modest steps. Don't hand it a large chunk of RAM up front; that just starves MySQL and PHP-FPM of memory they actually need.