Today I will be showing you how to install WordPress on Debian 13, using Nginx, MariaDB, and PHP 8.4. Even though I’m using Debian, these steps should apply to other distros like Ubuntu. I will also be showing how to install phpMyAdmin for database management, how to implement some basic security measures, and some basic PHP optimization.
Contents
The Purpose of This Post (Not Important)
When I make a post on here, I like focusing on more niche and interesting stuff, or at least guides that are difficult to find elsewhere. You might be asking yourself “If you like focusing on more niche stuff, then why are you writing yet another WordPress installation tutorial?” The answer boils down to these two reasons:
- In 2022, I wrote a series of guides about installing WordPress on Debian 11 (Part 1, Part 2, Part 3, and Part 4). However, they were my first ever blog posts, and looking back at them now that I’m much more experienced, I’m not happy about them, so I’ve been wanting to make an updated version of them.
- I plan on using this guide as a reference for future and much more interesting posts, and I don’t want to direct people to my old, outdated, and not very good posts.
Now that we’ve got the “Why?” out of the way, let’s move on to actually installing WordPress.
Prerequisites
Before you can install WordPress, it’s important to get a few things out of the way:
- Make sure that your Debian install (Or Ubuntu, or similar) is up to date.
- This guide is written with the assumption that you’re running a bare-bones Debian 13 install with nothing else running on it, so if you’re running other services, the configuration you’ll need might be different.
- Since this is a very easy thing to overlook, make sure that whatever ports you’ll need are open on your system and on any firewalls in between (80 and 443 for WordPress, 8080 for phpMyAdmin, change as needed), otherwise you’ll spend way too much time troubleshooting.
- If you use Ubuntu, you can check if UFW, the default firewall, is enabled by running
sudo ufw status. Debian doesn’t seem to have a firewall installed. You can allow a port by runningsudo ufw allow 80, for example.
Installing Nginx
No good website is complete without a web server, so all you need to do to install Nginx is run the following command:
sudo apt install nginx
Next, enable Nginx to run at startup:
sudo systemctl enable --now nginx
The command should complete successfully. Type the IP address or domain of your server into your web browser, and you should see the default welcome page:

If you don’t, check to see if Nginx is running correctly:
sudo systemctl status nginx
You should also check the log files in /var/log/nginx for additional information.
And of course, check that any firewall running on your server or any firewall between your client and server is not blocking anything. You can run curl http://127.0.0.1 and curl http://ip-or-domain on your server to see if it’s a server or network issue.
Nginx Configuration
Now that you’ve verified that Nginx is working, remove the default virtual host to prevent any conflicts:
sudo rm /etc/nginx/sites-enabled/default
I recommend just deleting the symlink in /etc/nginx/sites-enabled rather than also deleting the default virtual host in /etc/nginx/sites-available, just to keep it as a reference and for troubleshooting.
Next, create a new virtual host file:
sudo nano /etc/nginx/sites-available/wordpress
And paste the following content, adjusting as needed:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/wordpress;
index index.php index.html index.htm index.nginx-debian.html;
# We will come back to this later when we configure Certbot for HTTPS, but for now, a catch-all is fine
server_name _;
# Change this to a more rational value if you want an actual limit
client_max_body_size 999G;
location / {
try_files $uri $uri/ /index.php?$args;
}
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options "SAMEORIGIN";
}
# YoastSEO sitemap support, it's best to remove if you don't have YoastSEO installed to prevent any conflicts
location ~ ([^/]*)sitemap(.*).x(m|s)l$ {
rewrite ^/sitemap.xml$ /sitemap_index.xml permanent;
rewrite ^/([a-z]+)?-?sitemap.xsl$ /index.php?yoast-sitemap-xsl=$1 last;
rewrite ^/sitemap_index.xml$ /index.php?sitemap=1 last;
rewrite ^/([^/]+?)-sitemap([0-9]+)?.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;
}
# Don't log access to favicon.ico and robots.txt
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Deny access to hidden files
location ~ /\.ht {
deny all;
}
# Prevent PHP from running from upload directories
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
# Caching and gzip configuration, change as you see fit
location ~* \.(jpg|jpeg|gif|png|webp|svg|woff|woff2|ttf|css|js|ico|xml)$ {
expires 30d;
log_not_found off;
}
# Most stuff should already be covered under gzip_types, but you can add or remove as needed
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/rss+xml
application/atom+xml
application/xhtml+xml
image/svg+xml
image/x-icon
font/ttf
font/otf
font/woff
font/woff2
application/vnd.ms-fontobject;
gzip_proxied any;
}
This is an Nginx config that I have somewhat written myself, using the default config as a base, enabling PHP and caching support, and adding a few bits for WordPress.
After you’ve saved the file, symlink it to /etc/nginx/sites-enabled/wordpress:
sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/wordpress
Next, check to see if there’s any errors:
sudo nginx -t
If there aren’t, then you should see the following output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Finally, restart Nginx:
sudo systemctl restart nginx
If you try accessing your server in your web browser, you’ll get a 404 error, since WordPress hasn’t been installed or configured yet. Regardless, it’ll tell you that Nginx at least sees your new virtual host.
Installing PHP 8.4
PHP is what makes WordPress tick, so we’ll need to install it. First, install the php-fpm package, along with all of the modules that WordPress needs:
sudo apt install php-fpm php-mysql php-curl php-dom php-imagick php-mbstring php-zip php-gd php-intl
Next, enable and start the php8.4-fpm service:
sudo systemctl enable --now php8.4-fpm
Configuring php-fpm
Most of the defaults are fine, but some of them need changing. For example, you can only upload up to 2MB files, which is just way too small. Here’s a couple of commands to fix that:
# Change 999G if you want some sort of limit
sudo sed -i "s/^post_max_size = 8M/post_max_size = 999G/g" /etc/php/8.4/fpm/php.ini
sudo sed -i "s/^upload_max_filesize = 2M/upload_max_filesize = 999G/g" /etc/php/8.4/fpm/php.ini
“Optimizing” pm_max_children
By default, php-fpm will only spawn up to 5 child processes, which is usually not enough for WordPress. This results in a lot of warnings in your logs saying that the limit of child processes has been reached, and can even cause performance issues.
The reason why I put “optimizing” in quotes is because there’s two ways of doing this. The first way is the right way, which involves going into www.conf, setting pm = dynamic (The default), doing some math, and setting the values based on how much memory your server has.
But since nobody wants to do math, I’ve come up with an easier way. My way involves setting pm = ondemand and pm.max_children to some absurdly high number, effectively removing the limit:
# Change 99 to something lower if you want some sort of limit
sudo sed -i "s/^pm = dynamic/pm = ondemand/g" /etc/php/8.4/fpm/pool.d/www.conf
sudo sed -i "s/^pm.max_children = 5/pm.max_children = 99/g" /etc/php/8.4/fpm/pool.d/www.conf
Next, restart the php-fpm service:
sudo systemctl restart php8.4-fpm
Now php-fpm will spawn as many processes as it wants, and thanks to pm = ondemand, it’ll terminate idle ones after 10 seconds. You’ll most likely never need to worry about it again, and your server (probably) won’t explode. My method technically causes some overhead, since it needs to keep track of those processes, but probably not enough to be noticable.
Installing MariaDB
Now that we’ve installed a web server and PHP, the last piece is installing a database for WordPress. First, install MariaDB:
sudo apt install mariadb-server
Next, enable and start MariaDB:
sudo systemctl enable --now mariadb
Then run the included script to secure MariaDB:
sudo mariadb-secure-installation
Note: Depending on your distro and MariaDB version, the command might be mariadb_secure_installation, mysql_secure_installation or mysql-secure-installation. mariadb-secure-installation is what the command is on Debian 13 and MariaDB 11.8.
On the first step, it’ll ask you for the root password on your server, but if you don’t have one and you’re using sudo instead, just press enter. You can also say no for changing MariaDB’s root user password if you’re not using phpMyAdmin, or will create a different user. For everything else, choose yes.
Installing phpMyAdmin (Optional)
Even though you don’t need phpMyAdmin, I still like having it since it makes database maintenance (if needed) easier, and it has fun statistics graphs to watch. Regardless, if you’re a command line junkie and don’t care about that stuff (And want a minimalish install), then move on to “Creating The WordPress Database (CLI)”.
First, install the phpMyAdmin package:
sudo apt install phpmyadmin
It’ll ask you a few questions during installation. The first one it’ll ask is what webserver to configure automatically. Since we’re using Nginx, don’t select anything, press tab so your cursor is over <Ok>, and hit enter:

It’ll then ask you if you want to configure a database for phpMyAdmin. Select yes, which is the default:

Next, create a password for the phpMyAdmin user (Or leave it blank to have it generate a random one:

Configuring Nginx for phpMyAdmin
Since phpMyAdmin can’t configure Nginx automatically, we’ll have to do it ourselves. First, create a new virtual host file for phpMyAdmin:
sudo nano /etc/nginx/sites-available/phpmyadmin
Next, add the following content, which spoiler alert, is basically the same as the WordPress one, just with the WordPress specific stuff stripped out:
server {
# Change this to 80 if you want to access it with a subdomain instead
listen 8080 default_server;
listen [::]:8080 default_server;
root /usr/share/phpmyadmin;
index index.php index.html index.htm index.nginx-debian.html;
# If you want it to be accessible on a subdomain, replace _ with your subdomain, and make sure you have a DNS record pointing to your server
server_name _;
location / {
try_files $uri $uri/ /index.php?$args;
}
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options "SAMEORIGIN";
}
# Don't log access to favicon.ico and robots.txt
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Deny access to hidden files
location ~ /\.ht {
deny all;
}
}
Next, symlink it to sites-enabled:
sudo ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/phpmyadmin
Test it:
sudo nginx -t
And finally, if it was successful, restart Nginx:
sudo systemctl restart nginx
Now navigate to server-ip-address:8080 (Or your subdomain), and if everything has gone well, you should be greeted with the phpMyAdmin login screen:

Try logging in with the root user, using the password you set (Or the phpMyAdmin user, which is phpmyadmin, but you’ll need root later on), and you should be greeted with the home page:

If it doesn’t let you log in, you might need to reset the root password:
sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('Password') OR unix_socket;"
Or if you don’t want to use root, you can create a new user and disable the root user instead:
sudo mysql -e "CREATE USER 'notroot'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('Password');"
sudo mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'notroot'@'localhost' WITH GRANT OPTION;"
sudo mysql -u notroot -p
MariaDB [(none)]> ALTER USER 'root'@'localhost' ACCOUNT LOCK;
You can perform these steps in phpMyAdmin instead, besides resetting the root password.
Additional phpMyAdmin Security
A mistake that people make far too often when configuring phpMyAdmin is leaving it wide open to the internet, and on top of that, using the default root user with a weak, or God forbid, no password. Not only can an attacker gain access to any database on your server, including your WordPress one, but it can also be used as an entrypoint to gain access to the rest of your server.
We’ve already taken half of the measures we need by locking down the root user, so let’s focus on protecting phpMyAdmin itself by requiring a separate login to access it. First, install the apache2-utils package:
sudo apt install apache2-utils
Next, create a .htpasswd file in /etc/nginx. Make sure that you choose a different login from phpMyAdmin and WordPress, otherwise it’s pretty pointless:
sudo htpasswd -c /etc/nginx/.htpasswd username
You’ll be prompted twice to enter a password. Next, change the permissions on the file to more secure ones:
sudo chown www-data:www-data /etc/nginx/.htpasswd && sudo chmod 640 /etc/nginx/.htpasswd
This will ensure that www-data, the Nginx user, is the only user that can access the file. Finally, modify the location / block in the phpMyAdmin virtual host as follows:
...
location / {
try_files $uri $uri/ /index.php?$args;
# Basic authentication
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.htpasswd;
}
...
For extra security, I would also recommend combining it with IP address rules:
...
location / {
try_files $uri $uri/ /index.php?$args;
# Requires that the user both has an allowed IP address and their login is correct
satisfy all;
# Your public IP address
allow 1.2.3.4;
# Your LAN (If applicable)
allow 192.168.0.0/24;
# You probably get the idea by now, add and remove as needed
# Deny everyone else
deny all;
# Basic authentication
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.htpasswd;
}
...
Check your configuration and restart Nginx:
sudo nginx -t && sudo systemctl restart nginx
If all is well, try navigating to phpMyAdmin, and you should be prompted for a login from Nginx:

If you entered your login correctly, but it’s not letting you in, check /var/log/nginx/error.log and see if it’s having issues accessing .htpasswd. If it’s having trouble accessing it, you can try giving everyone access (sudo chmod a+r /etc/nginx/.htpasswd) temporarily to see if that fixes it. Giving everyone access is very insecure, so make sure to change it back once it’s working.
You can also recreate your user if you forgot your password (Or as a quick sanity check):
sudo htpasswd /etc/nginx/.htpasswd username
If you combine this with HTTPS (Certbot makes this easy) and Fail2ban (Updated guide coming soon, I promise), then phpMyAdmin will be safe from the vast majority of attackers.
Creating The WordPress Database (phpMyAdmin)
After you’ve made your way into phpMyAdmin one way or another, click the new button to create a new database:

Next, enter a name for your WordPress database, and select utf8mb4_general_ci from the dropdown menu, then click Create:

Since we now need a user to securely access this database, click the phpMyAdmin in the top left corner to navigate back to the home page, then click the User accounts tab:

Under User accounts overview, click Add user account:

You can choose whatever username or password you want, but I would recommend entering localhost for Host name since that will only allow logins for that user from your server, and using the option to randomly generate a password (You’ll need to temporarily store it somewhere, since we’ll need it again):

Scroll to the bottom of the page, then click the Ok button towards the bottom left, under the SSL box:

You should be brought back up to the top of the page, and a message should appear stating that your user was created successfully.
Next, click the Database tab, select your WordPress database, then click Go:

Click Check all under Database-specific privileges to give your WordPress user full access to your WordPress database, then click Go in the bottom left corner:

Creating The WordPress Database (CLI)
If you don’t use phpMyAdmin, or just want a more straight-forward way of creating a database and user, you can more easily do it via the MariaDB command line.
First, connect to MariaDB and create a database for WordPress:
# Replace notroot with your own user, or just use sudo mysql if you didn't set a password for root
mysql -u notroot -p
MariaDB [(none)]> CREATE DATABASE wordpress;
Next, create a user for your WordPress database, and give it full permissions:
# You'll want to randomly generate a password
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'VerySecurePassword';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
Now I don’t know about you, but I personally find this method a lot easier, or at least a lot easier to write about.
Actually Getting WordPress
We’re almost done with the hard part. We installed and configured Nginx, then PHP 8.4, and now MariaDB, so now that our server is setup, it’s time to actually download WordPress.
First, install a couple of packages that will let you download and unzip WordPress into your web directory. These packages might already be included depending on your distro, but for a very bare bones Debian install, they’re not:
sudo apt install curl unzip
Next, download the WordPress ZIP file. I would recommend saving it to /tmp since you don’t need to worry about deleting it, but it doesn’t matter. Just don’t forget to delete it when you’re done:
curl https://wordpress.org/latest.zip -o /tmp/wordpress.zip
Next, unzip the WordPress archive to /var/www/html:
sudo unzip -d /var/www/html /tmp/wordpress.zip
Navigate to /var/www/html/wordpress, create a copy of wp-config-sample.php, and name it wp-config.php:
cd /var/www/html/wordpress && sudo cp wp-config-sample.php wp-config.php
Next, open wp-config.php in a text editor, locate the database connection settings, and fill in your WordPress database information:
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'database_name_here' );
/** Database username */
define( 'DB_USER', 'username_here' );
/** Database password */
define( 'DB_PASSWORD', 'password_here' );
/** Database hostname */
define( 'DB_HOST', 'localhost' );
Finally, change the owner of the WordPress folder to Nginx’s user:
sudo chown -R www-data:www-data /var/www/html/wordpress
Finishing The Install
Try navigating to your server’s IP address or domain like you did earlier when testing Nginx, and you should see a screen asking you for your language. Your browser may try to redirect you to HTTPS, so make sure that it’s only using HTTP:

If you see it, then congratulations! You’ve successfully finished the hard part, and can continue setting it up. If you don’t, or you’re having problems, then move to the troubleshooting sections and come back.
Click continue, and fill out your information:

After clicking Install WordPress, you should now see the WordPress admin page:

At this point, you should click on Updates under Dashboard, and make sure that everything is up to date. You should also navigate to Plugins, Installed Plugins, and click the Enable auto-updates link for every plugin that’s there. You’ll also need to do this to any new plugins that you install, since for whatever reason it’s disabled by default.
Troubleshooting
When installing WordPress, especially for the first time, there are some common issues that me (Even after doing quite a few of them) and many others run into, so I’ll outline some common issues and their solutions.
Error Establishing a Database Connection
One of the most common errors you get when setting up WordPress is that it can’t connect to your database (Usually it doesn’t display the troubleshooting steps):

The first step is to check and see if MariaDB is running, so run the following command:
sudo systemctl status mariadb
This command will also show if MariaDB is producing any errors.
The next step is to ensure that the login credentials and database name in wp-config.php are correct, and that there aren’t any syntax issues with the file. As a sanity check, you can delete the wp-config.php file and copy it again from wp-config-sample.php (Assuming you’re in /var/www/html/wordpress):
sudo rm wp-config.php && sudo cp wp-config-sample.php wp-config.php && sudo chown www-data:www-data wp-config.php
Next, it’s time to verify that your WordPress database and user actually exist. First, connect to your database using the same commands you used earlier, and run the following command to show all of the databases on your server:
SHOW DATABASES;
If you’re using phpMyAdmin, your WordPress database will appear on the left side of the screen along with all of the other databases on your server.
Next, verify that your WordPress user exists:
SHOW CREATE USER 'wordpress'@'localhost';
If your WordPress user exists, it’ll show the same CREATE USER command that you used when creating the user (The real password being replaced with a hash).
If you forgot the password you set for your WordPress user, or it happens to contain a single ' or double quote " (Which might break the syntax in wp-config.php), you can change it with the following command:
ALTER USER 'wordpress'@'localhost' IDENTIFIED BY 'VerySecurePassword';
For phpMyAdmin users, you can navigate to the same User accounts screen that you used when creating your WordPress user, and if you want to change its password, click on its name, then Change password towards the top of the screen.
Finally, to verify that your WordPress database is accessible by your user, log into phpMyAdmin or connect using your WordPress user:
sudo mysql -u wordpress -p
Then run the following commands to connect to your database:
SHOW DATABASES;
# If your WordPress database is in the list, then try using it
USE wordpress;
SHOW TABLES;
# It'll say it's empty, which is normal
If you’re getting any errors, switch back to your root account, and try giving the WordPress user all privileges on your WordPress database:
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
A database permission issue may also manifest as WordPress letting you complete the setup, but it failing at installation, or otherwise giving permission issues.
If needed, update your login credentials in wp-config.php, and refresh the page. You should now be prompted for your language.
Permission Issues or Installation Failing
If WordPress is failing at the installation step, or it let you installed, but you keep getting permission errors when you try doing anything, then this can indicate a permission issue on the WordPress files, or a permission issue with your database.
First, connect to MariaDB, and try giving your WordPress user full permissions on your WordPress database:
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
If you’re using phpMyAdmin, the steps are basically identical to creating the user, except under User accounts, you just select your WordPress user and click Database at the top.
You can try logging in as your WordPress user and try accessing the database (Which the steps for I’ve already outlined at the end of the previous section).
Next, verify that the Nginx user has access to WordPress’s files:
sudo chown -R www-data:www-data /var/www/html/wordpress
You should now be able to access WordPress without getting any permission issues.
Browser Issues (Connection Timed Out, Connection Refused)
If you try accessing WordPress, but your browser is timing out, or giving an error like connection refused, then that usually indicates a problem with Nginx.
First, make sure your browser is not trying to connect over HTTPS. Browsers like Firefox will show a warning message stating that the website doesn’t support HTTPS before letting you continue, but some browsers will redirect regardless, and fail. You might need to type in your server’s address as http://ip-address-or-domain, making sure you type http instead of https, since that usually forces browsers to use HTTP rather than HTTPS. You can also temporarily disable any features that automatically redirect you to HTTPS to ensure they’re not the problem.
If you’re behind Cloudflare or another CDN provider, they might be trying to redirect you to HTTPS, so make sure that those features are disabled. After that, try clearing your CDN provider’s cache and accessing WordPress in a private browsing session.
Once you’ve ruled out browser issues, it’s time to narrow it down to the network or your server. The easiest way to do this is to have your server send a request to itself using curl:
curl http://127.0.0.1
# Alternatively
curl http://localhost
# You can use your server's IP address or domain instead
curl http://server-ip-or-domain
You should get the response printed directly to your terminal. If it fails, check the status of the Nginx service:
sudo systemctl status nginx
If you try to restart the Nginx service (sudo systemctl restart nginx), and it fails, check your config for any errors:
sudo nginx -t
You can also check the log files in /var/log/nginx for any errors as well.
Basic Security
Since this blog post is already long enough, I will only leave a couple of security tips here that are simple to implement, yet so many people neglect to do, despite being mentioned by basically every WordPress guide.
Securing wp-admin
Despite being one of the most well known vulnerabilities of WordPress, many people still leave /wp-admin wide open to the internet, with weak credentials, and typically no security measures.
The first thing you should do is use secure login credentials. Generate a random password with special characters, and make it as long as WordPress allows.
The next thing you should do is limit access to /wp-admin and /wp-login.php. You can accomplish this by making Nginx require a separate login that’s different from your WordPress login. The steps used when securing phpMyAdmin are basically identical, but there’s still some stuff you should know.
First, install the apache2-utils package so we can create a .htaccess file:
sudo apt install apache2-utils
Next, create your user. Again, make sure that this login is different from WordPress and anything else on your server, otherwise you’re wasting your time:
sudo htpasswd -c /etc/nginx/.htpasswd username
# Or if .htpasswd already exists when we secured phpMyAdmin, name it something else
sudo htpasswd -c /etc/nginx/.something-else username
Next, change the permissions on your .htaccess file to something more secure, replacing the file name with the one that you chose:
sudo chown www-data:www-data /etc/nginx/.something-else && sudo chmod 640 /etc/nginx/.something-else
After that, open /etc/nginx/sites-available/wordpress in a text editor, and locate the location / block. Under that, add in two blocks for /wp-admin and /wp-login.php:
...
# The default location / block for reference. You don't need to add this
location / {
try_files $uri $uri/ /index.php?$args;
}
# Again, change .something-else to the file name that you used
location /wp-admin {
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.something-else;
}
location = /wp-login.php {
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.something-else;
# Copy the PHP stuff from the location ~\.php$ block
# There's probably a better way to do this, but I also don't feel like figuring it out
include snippets/fastcgi-php.conf;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options "SAMEORIGIN";
}
For yet an extra layer of security, I recommend adding IP address rules to further restrict access:
...
# Again, probably a better way to do this, but this works fine
location /wp-admin {
satisfy all;
# Your public IP address
allow 1.2.3.4;
# Your LAN (If applicable)
allow 192.168.0.0/24;
# Add or remove as needed, make sure deny all is at the bottom
deny all;
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.something-else;
}
location = /wp-login.php {
satisfy all;
allow 1.2.3.4;
allow 192.168.0.0/24;
deny all;
auth_basic "Login Required";
auth_basic_user_file /etc/nginx/.something-else;
...
}
...
Finally, tell Nginx to test your config, restart Nginx (You probably know the commands by now), and try navigating to /wp-admin and /wp-login.php. Nginx should prompt you for a login separate from your WordPress one:

Now /wp-admin and /wp-login.php have some extra layers of security, especially if you combine Fail2ban to block brute force attacks (Again, post coming soon enough, I promise).
If you’ve typed in the right credentials but it’s not letting you login, check /var/log/nginx/error.log to see if it’s having any issues accessing your .htpasswd file. You can try running sudo chmod a+r /etc/nginx/.something-else (Obviously substituting with your own .htpasswd file) to see if that fixes it, but make sure to change this back since it’s very insecure.
Disabling /xmlrpc.php
The /xmlrpc.php endpoint was used by WordPress to allow applications to communicate to your website, like the WordPress mobile app for example. However, nowadays it has been replaced by the REST API (Located at /wp-json), and is only used for backwards compatibility.
Regardless, unless if you plan on using ancient applications to talk to your website, all it does is serve as a flashing neon billboard for attackers. Despite this, not only is it enabled by default, but WordPress doesn’t include an option to disable it (Which did exist in the early days after its introduction, but was apparently removed).
The two common ways of disabling /xmlrpc.php is by using a plugin, or blocking it on your webserver. I’ll be showing how to block it with Nginx, so you don’t need to install any plugins.
First, fire up your WordPress virtual host file, and navigate to right where we left off in the previous section. Under your location = /wp-login.php block, add in a new one for /xmlrpc.php:
# You can add an allow before the deny to allow certain IP's in the slim chance that you happen to need it, but you probably don't
location = /xmlrpc.php {
deny all;
}
Test your config and restart Nginx, then try nagivating to /xmlrpc.php, like you did with /wp-admin and /wp-login.php. You should get a 403 forbidden error.
Configuring HTTPS
If you’ve made it this far, and not only your server isn’t on fire, but you haven’t gone insane (Or lost me), then congratulations! You’ve made it to the home stretch, and we only have one last step left, which is configuring HTTPS. To make it easy, I’ll show how to use Certbot.
Before we can do anything, it’s important to note that WordPress seems to listen on one URL and one URL only, and that URL is set when you first install WordPress. This means that since you’ve probably accessed your server with its IP address or http://yourdomain.com, WordPress will automatically redirect you to it.
If you try setting up HTTPS without first changing this URL, you’ll either just get redirected to your server’s IP address, or get a redirect loop since Certbot tells Nginx to redirect to HTTPS, but WordPress keeps trying to redirect to HTTP.
Thankfully, it’s easy to change this URL. You can either do this in /wp-admin under Settings and General, or modifying the WordPress database, which is what I’ll show. Keep in mind, you WILL temporarily loose access, but you can change it back if needed by modifying the database.
First, connect to your database using your WordPress user, then switch to your WordPress database:
mysql -u wordpress -p
MariaDB [(none)]> USE wordpress;
Take note of your existing URL settings so you can easily revert back to it if needed:
SELECT * FROM wp_options WHERE option_name IN ('siteurl','home');
# Your current URL will be displayed under option_value
Once you’ve taken note of your existing URL, change it to your new one:
UPDATE wp_options SET option_value = "https://changeme.alexshomenetwork.com" WHERE option_name = "siteurl" OR option_name = "home";
Now that you’ve updated your site URL’s, you can now install Certbot:
sudo apt install certbot python3-certbot-nginx
So that way Certbot actually sees your website, open your Nginx virtual host file WordPress, and locate server_name _; towards the top:
# Remember me mentioning this earlier? Change the _ you set earlier to your domain
server_name changeme.alexshomenetwork.com;
# I put my domain there just as an example
If you’re accessing phpMyAdmin from a subdomain, then your config should already be good.
You know the Nginx drill by now. It’s a good time to verify that the correct DNS records are pointing to your server, and you can actually access your server from the public internet, from both HTTP and HTTPS, otherwise Certbot will fail. If you’re behind a CDN like Cloudflare, you should disable any features that automatically redirect you to HTTPS, and purge their cache.
Next, run the certbot command, and follow the prompts. If you don’t want to do it interactively, you can use flags instead (certbot --help):
sudo certbot
Certbot should automatically restart Nginx for us, but if it doesn’t, do our ususal Nginx drill, and try accessing your website.
If you can’t access your website, you might need to purge your cache with your CDN provider like Cloudflare if you’re behind one. I also recommend checking in a private browsing session, since your browser cache can cause issues as well.
Conclusion
If your website is working, then congratulations! You’ve installed WordPress from start to finish, and even implemented some basic security measures. Like I mentioned a couple of times earlier, I do plan on writing an updated Fail2ban guide (Which I will link here). If you have any questions, feel free to leave them below, and I’ll try my best to help you, even though I can’t make any guarantees.
References
Nginx Authentication: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
Database Creation: https://developer.wordpress.org/advanced-administration/before-install/creating-database/
Where my Nginx Config is Derrived From: https://developer.wordpress.org/advanced-administration/server/web-server/nginx/
Hello Alex,
thank you very much for time and effort you put into this guide.
I am working for more than 35 years as an it consultant.
Historically not beeing that deep into linux and web technologies due to a different focus i use web guides like yours quite often to extend my knowledge.
Believe it or not:
Yours is the first ever in all this years that worked flawless on my first try.
Great job!
Arne
Thank you for the kind words
I used this tutorial to install my wordpress and after a few tweaks it worked like a charm. Thank you very much <3
No problem, glad that my guide was able to help you.
Thank you very much Alex!!!