How to install WordPress on a VPS
Running WordPress on your own VPS gives you control over the application, database and backups. This tutorial deploys the official WordPress and MySQL containers with Docker Compose, puts them behind HTTPS and explains the first configuration and maintenance tasks.
Prerequisites
Prepare a fresh Ubuntu 22.04 or 24.04 VPS with SSH access, a public IP and at least 2 GB of RAM. Use more memory if you expect several plugins, frequent image processing or concurrent editors. Create an A record such as www.example.com pointing to the server and make sure the provider firewall allows SSH, HTTP and HTTPS.
You also need a local SSH client, Docker knowledge and a backup destination outside the VPS. WordPress is easy to start and easy to lose if the database and uploads are not copied somewhere else.
Step 1, Connecting to your server
Connect and update the host:
ssh root@SERVER_IP
apt update && apt upgrade -y
apt install -y ca-certificates curlCreate a dedicated directory with restricted permissions:
mkdir -p /opt/wordpress
cd /opt/wordpress
chmod 750 /opt/wordpressDo not run WordPress from your home directory if other users can access it. Keep the Compose file and environment file readable only by administrators.
Step 2, Installing Docker and Docker Compose
Install Docker Engine and the Compose plugin by following Docker's current Ubuntu instructions. Verify the runtime before creating the stack:
docker --version
docker compose version
systemctl enable --now dockerThe Compose plugin is invoked as docker compose, not necessarily as the older docker-compose binary. Prefer the current command so that the same file works with a supported Docker installation.
Step 3, Running WordPress with Docker Compose
Create an .env file and replace every placeholder before starting. Keep this file out of Git and use long random values for both database passwords:
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=REPLACE_WITH_A_RANDOM_VALUE
MYSQL_ROOT_PASSWORD=REPLACE_WITH_ANOTHER_RANDOM_VALUE
WORDPRESS_DB_HOST=db:3306
WORDPRESS_DB_NAME=wordpress
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=REPLACE_WITH_A_RANDOM_VALUECreate compose.yaml:
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
- db
environment:
WORDPRESS_DB_HOST: ${WORDPRESS_DB_HOST}
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
ports:
- "127.0.0.1:8080:80"
volumes:
- wordpress_data:/var/www/html
volumes:
db_data:
wordpress_data:The default wordpress:latest image bundles Apache and is suitable for most setups. Beginners should start with this image. The fpm variant requires an external web server; if you switch to it, never publish port 9000 to the host โ exposing the FPM socket is a security risk.
Since March 2021 the official WordPress Docker image automatically generates wp-config.php from the supplied environment variables on first start. Do not create or edit this file inside the container โ configure WordPress entirely through environment variables.
Start the stack and check its state:
docker compose up -d
docker compose ps
docker compose logs --tail 100 wordpressThe database can take a moment to accept connections. Do not delete db_data while diagnosing a connection error.
Step 4, Configuring the reverse proxy
The WordPress container listens only on localhost port 8080, so it is not directly exposed to the internet. Put it behind a host proxy such as Caddy or Nginx, or use the proxy already provided by your VPS platform. The proxy should forward requests for www.example.com to 127.0.0.1:8080 and obtain a certificate through its supported ACME workflow.
Open ports 80 and 443 in the provider firewall and the host firewall. Keep port 8080 closed publicly. Configure WordPress's site URL with the final HTTPS address during the browser setup, otherwise login cookies and redirects can use the wrong scheme. If WordPress sits behind an HTTPS proxy, the reverse proxy must forward the X-Forwarded-Proto header so that WordPress generates correct HTTPS links. Without it the login page and admin redirects cause infinite redirect loops.
Step 5, First access and initial setup
Visit https://www.example.com. Choose the language, set the site title and create the administrator account with a unique password. Delete any unused sample content, update the permalink structure and install only plugins you have reviewed.
Set the correct timezone and check that media uploads work. If the site is behind a proxy, verify that the dashboard reports HTTPS and that canonical redirects do not loop. Create a non-administrator editor account for routine publishing.
Maintenance
Back up both named volumes โ wordpress_data (themes, plugins, uploads) and db_data (database) โ as well as the Compose file and .env file through a protected process. Test restoring the database and uploads on a separate machine. Update WordPress, plugins and images in a maintenance window, and read compatibility notes before changing the MySQL major version.
Watch disk, memory and container logs. If the site becomes unavailable, inspect docker compose ps, the WordPress logs and the database logs before restarting everything. A restart can hide the first useful error.
Tools mentioned
WordPress
โOpen-source CMS powering a large portion of the web, with plugins, themes and a built-in block editor.
Completely free self-hosted software. Host it anywhere with PHP and MySQL.