Installing WordPress on a VPS#
WordPress runs a quarter of the web for a reason: once installed properly, it keeps working. "Properly" is doing a lot of work in that sentence, and this page is about that part.
The shortcut
If you have connected VeriTeknik MCP to your AI assistant (Claude, ChatGPT, Cursor...), one sentence is enough:
"Open a server for example.com, point the domain at it, install WordPress with nginx, MariaDB and PHP, and set up HTTPS."
Your assistant opens the server from your balance, adds the DNS record if your domain is on VeriTeknik DNS, installs the packages, configures nginx and gets the certificate. The database needs no password (the password-free option in step 2), and you choose the site name and the admin account yourself in the WordPress wizard at the end; so you never need to type a password into the chat. ChatGPT cannot open servers: get the server in the panel and ChatGPT does the rest. The permissions to turn on are in the deployment guide.
What follows is for doing it yourself, or for understanding what your assistant did.
0. Point the domain#
Your A record must point at the server's IP. Do this first, because the certificate step depends on it.
dig +short example.com
1. Install the packages#
sudo apt update
sudo apt install -y nginx mariadb-server php-fpm php-mysql \
php-curl php-gd php-mbstring php-xml php-zip php-intl unzip
sudo dnf install -y nginx mariadb-server php-fpm php-mysqlnd \
php-curl php-gd php-mbstring php-xml php-zip php-intl unzip
sudo systemctl enable --now php-fpm
With Morpheus
"install the nginx, mariadb and php packages needed for WordPress"
Morpheus detects the distribution and picks the right package names - PHP package naming differs between Debian and RHEL, and you do not need to know that.
2. Prepare the database#
sudo systemctl enable --now mariadb
sudo mysql_secure_installation
mysql_secure_installation is interactive; you have to answer its questions. Set a root password, remove anonymous users and the test database.
Morpheus will not run this
This command is deliberately blocked at Morpheus' gate: interactive commands hang a non-interactive SSH session. Do this step yourself, or have Morpheus run the equivalent SQL.
Create the database and user:
sudo mysql -e "CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'PUT_A_STRONG_PASSWORD_HERE';"
sudo mysql -e "GRANT ALL ON wordpress.* TO 'wpuser'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"
Generate a genuinely strong password: openssl rand -base64 24
The password-free option
With MariaDB on Ubuntu and Debian you can create the database user with no password at all. PHP-FPM runs as www-data, and MariaDB recognises it by its operating-system identity (unix_socket), so there is no database password to store or to leak. It is also the path that works when your AI assistant does the install, because the connection will not run a command with a password written into it.
sudo mysql -e "CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
sudo mysql -e "CREATE USER 'www-data'@'localhost' IDENTIFIED VIA unix_socket"
sudo mysql -e "GRANT ALL ON wordpress.* TO 'www-data'@'localhost'"
On this path, create wp-config.php on the server with WP-CLI after step 3. The security keys are generated there too, and the wizard in step 6 skips the database questions and goes straight to the site name:
sudo curl -fsSL -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
sudo -u www-data wp config create --path=/var/www/example.com --dbname=wordpress --dbuser=www-data --dbhost=localhost
3. Put the files in place#
cd /tmp
curl -O https://wordpress.org/latest.tar.gz
tar xzf latest.tar.gz
sudo mkdir -p /var/www/example.com
sudo cp -r wordpress/* /var/www/example.com/
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Permissions
On Rocky/AlmaLinux the user may be nginx rather than www-data. Do not use 777 - most of the "this fixes the permission error" advice you find online actually opens your server up.
4. Nginx configuration#
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.php index.html;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
location ~ /\.(?!well-known) {
deny all;
}
}
The fastcgi_pass path varies by version; find yours:
ls /run/php/
sudo nginx -t && sudo systemctl reload nginx
The last two location blocks are security: they stop PHP executing from the uploads directory and block hidden files. That is the classic WordPress attack path.
5. Certificate#
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
6. Finish the install#
Go to https://example.com and complete the WordPress wizard using the database details from step 2; if you took the password-free option, the wizard does not ask for them.
Once done:
- Do not name the admin user
admin - Install a two-factor authentication plugin
- Delete unused themes and plugins; deactivating is not enough
7. Backups and maintenance#
WordPress needs two separate things backed up: files and database.
sudo mysqldump wordpress | gzip > /opt/backup/wp-db-$(date +%F).sql.gz
sudo tar czf /opt/backup/wp-files-$(date +%F).tar.gz -C /var/www example.com
A panel snapshot covers both and is easier.
With Morpheus
"back up the wordpress database every night at 03:00 and delete backups older than 7 days"
"check whether wordpress and its plugins have updates"
Never update without a backup
WordPress core updates are usually fine; plugin updates are not always. Take a snapshot first. Snapshots take minutes; repairing a broken site by hand takes days.
Checklist#
- [ ]
https://example.comopens with the padlock - [ ]
http://redirects tohttps:// - [ ] Admin username is not
admin, 2FA enabled - [ ]
sudo nginx -t→syntax is ok - [ ] PHP does not execute from the uploads directory (
deny allblocks in place) - [ ] A backup has actually been taken and a restore tested
- [ ]
sudo certbot renew --dry-runpasses - [ ] Unused themes and plugins deleted