What WordPress asks of the server
The wordpress.org requirements page recommends three things: PHP 8.3 or greater, MySQL 8.0 or greater or MariaDB 10.11 or greater, and HTTPS support, behind nginx or Apache with mod_rewrite. The Hosting Handbook, written for the people who run servers, is more specific. Each figure below carries its scope:
| Item | Official figure | Source and scope |
|---|---|---|
| PHP | 8.3 or greater | Requirements page, recommended |
| PHP in production | 8.4 or later | Hosting Handbook, notes for hosts |
| PHP floor | 7.4 | Minimum since WordPress 7.0; the requirements page calls such old versions end of life |
| Database | MySQL 8.0+ or MariaDB 10.11+ | Requirements page; the handbook dates MySQL 8.0 end of life to April 2026 and lists 8.4 LTS |
| Web server | nginx, or Apache with mod_rewrite | Requirements page |
| PHP memory_limit | 128M minimum, 256M default, 512M or more | Hosting Handbook; 512M for WooCommerce, page builders, multilingual plugins |
| PHP extensions | mysqli, json, hash required; curl, dom, exif, fileinfo, imagick, intl, mbstring, openssl, xml, zip highly recommended | Hosting Handbook |
WordPress publishes no CPU, RAM or disk figure, and neither does this page. Size from your own load: how many requests reach PHP without a cache in front, how heavy the plugins are, how fast the database and wp-content/uploads grow. Our working rule, not a documented one: PHP-FPM's pm.max_children times memory_limit, plus what MariaDB needs to keep its hot tables in memory, should fit in RAM with headroom.
Why the whole machine
On a dedicated server for web hosting nothing else runs on the box: every core and all the memory belong to PHP-FPM, MariaDB and nginx, and a busy neighbour cannot slow your sites down. Root means the PHP version, the web server, the object cache and the separation between sites are your choices.
- Traffic is not metered. The port is unmetered at every speed, so a launch or a post that spreads does not become a transfer bill.
- Our machines, our network. The servers and AS19624 are ours, with no reseller between you and the hardware.
- A console when SSH is gone. Rack servers and blades carry an out-of-band controller (HPE iLO, Dell iDRAC or a Supermicro BMC), published in the dashboard once you add a console login, so a bad firewall rule is fixed from a browser. Most Moonshot cartridges, including the Opteron X2150 and Atom builds below, have power control only.
Picking the build
The service page groups its hosting machines by what they give a website. Read them as classes:
- Lowest cost: a 4-core AMD Opteron X2150 cartridge, 8 GB, SATA SSD, 500 Mbps. A small site or a staging copy; tight for a shop whose workers each want 512M.
- SSD and 16 GB or more: an 8-core Intel Atom C2730. More PHP workers for a busy blog or a handful of small sites.
- NVMe and 32 GB or more: a quad-core Intel Xeon E3-1585L v5 at 3.00 GHz. Faster cores and NVMe for database-heavy work such as WooCommerce or memberships.
- Two SSDs in RAID 1: an 8-core Intel Xeon Silver 4110. The mirror survives one failed disk. It is not a backup.
Every one takes cPanel, Plesk or Nexo as an order option, or no panel if you follow the steps below. Nexo runs each account in its own PHP-FPM pool under its own user, the same separation this page builds by hand.
Install on Ubuntu 26.04 with nginx and WP-CLI
Ubuntu 26.04 ships PHP 8.5 and MariaDB 11.8 in its own archive, both on the handbook's supported lines. Do all of this before the domain's DNS points here. WordPress's web installer hands the site to whoever reaches it first; WP-CLI finishes the install from the shell, so that screen is never exposed. Firewall and packages first, as your sudo user:
sudo apt update && sudo apt -y full-upgrade
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw --force enable
sudo apt -y install nginx mariadb-server php-fpm php-mysql php-curl \
php-gd php-imagick php-intl php-mbstring php-xml php-zip \
certbot python3-certbot-nginx
sudo curl -o /usr/local/bin/wp \
https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
sudo chmod 755 /usr/local/bin/wp
wp --info
ufw writes IPv6 copies only of rules added while IPV6=yes, so the sed runs before the first allow, and --force keeps the confirmation prompt from eating the next pasted line. Then open a root shell with sudo -i for the site user, the database and WordPress. The database password is generated into a root-only file, reaches MariaDB on standard input and WP-CLI through --prompt, and from then on lives only in wp-config.php: never on a command line, never in shell history.
adduser --system --group --no-create-home --home /srv/example.com example
install -d -o example -g www-data -m 750 /srv/example.com /srv/example.com/public
install -d -o example -g example -m 700 /srv/example.com/backups
(umask 077; openssl rand -hex 24 > /root/example-db.pass)
mariadb <<SQL
CREATE DATABASE wp_example CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_example'@'localhost' IDENTIFIED BY '$(cat /root/example-db.pass)';
GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_example'@'localhost';
SQL
cd /srv/example.com/public
sudo -u example wp core download
sudo -u example wp config create --dbname=wp_example --dbuser=wp_example \
--dbcharset=utf8mb4 --prompt=dbpass < /root/example-db.pass
rm /root/example-db.pass
sudo -u example wp config set DISALLOW_FILE_EDIT true --raw
sudo -u example wp config set FORCE_SSL_ADMIN true --raw
chmod 400 wp-config.php
sudo -u example wp core install --url=https://example.com --title="Example" \
--admin_user=pick-a-name --admin_email=you@example.com --skip-email
With no --admin_password, wp core install generates one and prints it once; store it in your password manager. wp-config.php ends at mode 400, readable only by the user PHP will run as (the hardening guide asks for 400 or 440). Last, a PHP-FPM pool that runs this site as example, and an nginx server block adapted from the WordPress nginx article:
cat > /etc/php/8.5/fpm/pool.d/example.conf <<'EOF'
[example]
user = example
group = example
listen = /run/php/example.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 10
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
EOF
cat > /etc/nginx/sites-available/example.com <<'EOF'
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /srv/example.com/public;
index index.php;
client_max_body_size 64m;
location / { try_files $uri $uri/ /index.php?$args; }
location ~ /\.(?!well-known) { deny all; }
location ~* /(?:uploads|files)/.*\.php$ { deny all; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/example.sock;
}
}
EOF
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
nginx -t && systemctl restart php8.5-fpm && systemctl reload nginx
- Point the A record at the server, and an AAAA record too if you added IPv6 when ordering (it is free at checkout).
- Issue the certificate with
certbot --nginx -d example.com -d www.example.com. Until then the admin area refuses plain HTTP, becauseFORCE_SSL_ADMINis on. - Sign in at
https://example.com/wp-admin/and read Tools → Site Health.
Backups you schedule
On a server you run yourself, the backup schedule is yours. The WordPress backup guide sets the shape: the database and the files are separate and you need both; dump the database first; back up before every upgrade; daily for a high-activity site; keep three to five recent copies in different places. As root, crontab -u example -e and add:
20 3 * * * /usr/local/bin/wp --path=/srv/example.com/public db export /srv/example.com/backups/db-$(date +\%F).sql --quiet 2>> /srv/example.com/backups/cron.log40 3 * * * tar -czf /srv/example.com/backups/files-$(date +\%F).tar.gz -C /srv/example.com public 2>> /srv/example.com/backups/cron.log50 3 * * * find /srv/example.com/backups -type f -mtime +7 ! -name cron.log -delete
wp db export takes the credentials from wp-config.php, so the job holds no password. One dead array takes the server and any local backups down at once, so ship the backups directory to another machine every night. Our own advice: now and then restore one into a scratch database. The guide recommends an occasional manual backup on top of automated ones, to make sure the process is working.
Updates, caching and hardening
- Updates. The hardening guide says WordPress performs its updates as the user that owns the files. Our reading: because PHP-FPM runs as the site user here, WordPress can install its own updates. Minor core releases install automatically by default (
WP_AUTO_UPDATE_COREisminor). For the rest, after a backup:sudo -u example wp core update,sudo -u example wp plugin update --all,sudo -u example wp theme update --all. PHP, nginx and MariaDB come fromapt. - Caching. The WordPress cache article describes three kinds of cache, which you can combine: an opcode cache such as OPcache for PHP itself, a page-cache plugin (W3 Total Cache, WP Super Cache or Cache Enabler) that serves pages as static files, and a persistent object cache in Redis or Memcached. Running Redis on the same machine is covered in our Redis use case.
- Hardening. The theme and plugin editor is off (
DISALLOW_FILE_EDIT), the admin login is notadmin, PHP cannot run from uploads, and MariaDB has no port open in ufw. The hardening guide adds a separate database and database user per site, deleting plugins you no longer use, and reading your logs for brute-force attempts.
Many sites on one server
There are two ways to run many WordPress sites here, and they fail differently.
- Separate installs. Repeat the user, database, pool and server block for each site. Every site gets its own PHP user, database user and
wp-config.php, so one compromised plugin cannot read the other sites. That is the containment the hardening guide recommends; the price is one update run per site. - Multisite. One core, shared plugins and themes, separate tables and upload folders per site. Add
define( 'WP_ALLOW_MULTISITE', true );towp-config.php(give it mode 600 while you edit), then use Tools → Network Setup. Paste the lines Network Setup gives you intowp-config.php, then runchmod 400 wp-config.phpagain. Pick subdomains or subdirectories there: the docs warn that switching later may not be easy, and sites created on demand under subdomains need wildcard DNS. The nginx article has a server block for each layout. One bad plugin now reaches every site.
Selling WordPress sites to clients rather than running your own? A panel does the per-account separation and certificates for you; that is the job Nexo was built for.
When this is the wrong box
A brochure site with modest traffic does not need a machine of its own: web hosting runs it with no server to patch. A dedicated server makes you the administrator, so PHP and MariaDB upgrades, the backups above and mail delivery are yours; the install guide advises checking that email delivery works on your host, which password resets depend on. Moving an existing site? The migration guide walks through the cutover.
Questions
What is the smallest server WordPress will run on?
wordpress.org recommends PHP 8.3 or newer, MySQL 8.0+ or MariaDB 10.11+, and HTTPS. It still works on PHP 7.4 and MySQL 5.5.5 in legacy setups, which it warns are past end of life. The Hosting Handbook puts the PHP memory floor at 128M. No CPU or RAM minimum is published, so plugins and traffic set the size.
Can one dedicated server hold several WordPress sites?
Yes. Either give every site its own install, Linux user, PHP-FPM pool and database user, which keeps a compromise contained, or run Multisite, which shares one core and one set of plugins across all sites. Separate installs cost more update work; Multisite costs isolation.
Do I need cPanel or Plesk for WordPress?
No. With root you can run nginx, PHP-FPM and MariaDB yourself, as above, and a server with no panel carries no licence. A panel helps once you host many sites or hand accounts to other people; Plesk has WordPress tools built in, and Nexo isolates each account in its own PHP-FPM pool.
Is self-hosted WordPress the same software as the hosted kind?
Self-hosted means the open-source WordPress from wordpress.org running on a server you control. You pick the PHP version, the plugins and the themes, and you own the database and the files. In exchange, updates, backups and security are your job.
nginx or Apache for WordPress?
Both are on the requirements page. Apache with mod_rewrite lets WordPress write its own permalink rules into .htaccess. nginx has no .htaccess, so the rules live in the server block and WordPress cannot change them, one of the key differences the nginx article lists.
How soon is the server ready?
Machines already racked, taken as they stand, are handed over roughly half an hour once your payment clears approval. Builds to your own specification need between 4 and 24 hours.