Introduction
If you are looking to self host WordPress, using a LEMP stack is usually one of the best setups that you can do. Apache is good, but nginx offers a better performance while using fewer resources.
Today you will learn, step-by-step, how to put your website online using a fast and secure environment :)
Getting started
As always, the first thing you need is to make your VPS safe, once that is ok, you should update you Debian 11 using:
apt update && sudo apt upgrade
I also recommend you do restart the VPS before the full upgrade. Once that is done, let's clean our VPS.
apt autoremove --purge
Installing all the required packages (MYSQL, PHP-FPM, Redis, Nginx and certbot
MySQL 8
wget -c https://dev.mysql.com/get/mysql-apt-config_0.8.24-1_all.deb --inet4
dpkg -i mysql-apt-config_0.8.24-1_all.deb
On this screen, you can hit "ok" here:
rm mysql-apt-config_0.8.24-1_all.deb
apt update
apt install mysql-server
Choose a root password and make sure to save it on a safe place:
You can also choose "Use Strong password encryption"
Nginx
apt install nginx
systemctl enable nginx
PHP-FPM
For PHP, the best thing we can do to have the latest versions is using Sury's repository. The easiest way to install is to use his script:
wget -O installer.sh -c https://packages.sury.org/php/README.txt
bash installer.sh
rm installer.sh
Now, you can install any PHP FPM version. The one that I recommend is 8.1 as others are going to lose support soon:
apt install php8.1-fpm*
apt install php8.1-curl php8.1-redis php8.1-mysql php8.1-gd php8.1-intl php8.1-mbstring php8.1-soap php8.1-xml php8.1-xmlrpc php8.1-zip php8.1-imagick
systemctl enable php8.1-fpm
That will install all the php modules available for 8.1 as well as php-fpm 8.1 itself. This will also start php-fpm when the vps reboots.
Redis
curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/redis.list
apt update
apt install redis-server
systemctl enable redis-server --now
Certbot
apt install python3-certbot-nginx
WP CLI
wget -c https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar --inet4
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
Creating the database
Before we actually create our first database, it's a great idea to secure it. To do so, use:
mysql_secure_installation
For the first question, use "No":
For the second question, use "No" too:
For everything else, you can just type y and hit enter.
Now, you can safely create your database. To do so, enter on the database as the first thing:
mysql -p
Enter your root mysqlpassword and when you are inside the database, use these commands, replacing the database name and user by something real.
CREATE DATABASE DBNAME;
CREATE USER 'DBUSER'@'localhost' IDENTIFIED BY 'DBPASS';
GRANT ALL ON DBNAME.* TO 'DBUSER'@'localhost';
FLUSH PRIVILEGES;
EXIT;
TL;DR -> On the above, replace:
- DBNAME with your desired database name
- DBPASS with your desired password
- DBUSER with your desired database user
Isolating PHP-FPM
We will already isolate each WP installation on one user, but it's also a great idea to make php-fpm run under the user behalf. That way, PHP won't have root access and will be much harder for a plugin to modify your system using PHP.
To do so, we will need to create our own "php fpm pool":
cd /etc/php/8.1/fpm/pool.d/
cp www.conf yoursitename.conf
Now, enter on your file and edit the following:
- [www] -> The pool name. Change it to something else. I recommend using the username that you intend to use for the site.
- user = > Add the user that will run php fpm. Again, use the user that you intend to create
- group = > put the same thing that you used on user.
- listen -> On this one, edit the default path by adding a "-youruser" on the end. So after fpm.sock, edit it to fpm-youruser.sock
Since we are already in this file, it's a good idea too to change the PHP from "dynamic" to "ondemand":
There are discussions about which one is the fastest mode for performance, but I've great results with ondemand, specially in terms of ram usage. ondemand is perfect for smaller vps'ses to avoid ram errors and resources issues.
Finally, restart php fpm:
systemctl restart php8.1-fpm
Don't worry, you will get a error (that is expected) since the user that you added there doesn't exists yet. We will fix that soon.
Tuning PHP-FPM
Now, before we setup our user and Redis, it's a great idea to adjust php.ini values. That is pretty easy to do as you can just copy and past all the lines below and hit enter. I'm adding what I consider "optimal" values for WordPress that should ensure a smooth experience by default. Feel free to edit them as you prefer.
sed -i 's/memory_limit = 128M/memory_limit = 1024M/g' /etc/php/8.1/fpm/php.ini
sed -i 's/post_max_size = 8M/post_max_size = 10240M/g' /etc/php/8.1/fpm/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 10240M/g' /etc/php/8.1/fpm/php.ini
sed -i 's/max_file_uploads = 20/max_file_uploads = 1000/g' /etc/php/8.1/fpm/php.ini
sed -i 's/max_execution_time = 30/max_execution_time = 600/g' /etc/php/8.1/fpm/php.ini
sed -i 's/max_input_time = 60/max_input_time = 600/g' /etc/php/8.1/fpm/php.ini
sed -i 's/output_buffering = 4096/output_buffering = off/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;opcache.enable=1/opcache.enable=1/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;opcache.revalidate_freq=2/opcache.revalidate_freq=60/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;opcache.max_accelerated_files=10000/opcache.max_accelerated_files=20000/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;opcache.memory_consumption=128/opcache.memory_consumption=512/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;opcache.interned_strings_buffer=8/opcache.interned_strings_buffer=64/g' /etc/php/8.1/fpm/php.ini
sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/8.1/fpm/php.ini
Creating the WordPress nginx configuration
Now that we have our own php fpm pool and we have optimized the necessary settings, we can focus on creating the configuration file for WodPress.
To begin, let's go to one place where nginx reads config files on Debian:
cd /etc/nginx/sites-enabled
Inside this path, create a new config file with your site name and enter on it:
touch mysitename.conf
nano mysitename.conf
Ok so now, you need to copy the code code below, but make sure to replace the following:
- server unix:/tmp/php-cgi.socket with your actual socket path that we created on this step.
- server_name domain.tld with your actual domain (e.g: theselfhosting.art)
- root /home/youruser/public_html;with your real intended user (we will create the user in the next steps, so don't worry)
# Upstream to abstract backend connection(s) for php
upstream php {
server unix:/tmp/php-cgi.socket;
}
server {
client_max_body_size 10240M; #This sets upload limit to 10GB
## Your website name goes here.
server_name domain.tld;
## Your only path reference.
root /home/youruser/public_html;
## This should be in your http block and if it is, it's not needed here.
index index.php;
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location / {
# This is cool because no php is touched for static content.
# include the "?$args" part so non-default permalinks doesn't break when using query string
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_pass php;
#The following parameter can be also included in fastcgi_params file
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
}
Your code should look similar to this one:
Tuning and configuring Redis
We are close to the end. The last thing we need to do before installing WordPress is to setup Redis. To do so, just copy and paste the following lines:
sed -i 's/port 6379/port 0/g' /etc/redis/redis.conf
line_old='# unixsocket /run/redis.sock'
line_new='unixsocket /var/run/redis/redis-server.sock'
sed -i "s%$line_old%$line_new%g" /etc/redis/redis.conf
sed -i 's/# unixsocketperm 700/unixsocketperm 770/g' /etc/redis/redis.conf
sed -i 's/# maxmemory <bytes>/maxmemory 1024mb/g' /etc/redis/redis.conf
systemctl restart redis-server
This should ensure a great Redis Performance for our WP installation. You can increase the memory later if you have more sites to host.
Creating a new user
Finally, we will be creating the user! Remember when you added the path on the nginx config file? (/home/youruser/public_html)
Now it's time to create this user. Use the same intended user that you have used on the steps before.
Many people just install WordPress directly on the root user, but I think that is a bad idea for production. Using root directly will make things less secure as PHP will run as root and if your site gets hacked somehow, it will be easier to get the full VPS access.
Additionally, if you are using root and hosting more than 1 site, malware in one site usually means malware on all sites as they are all sharing the same file permissions. So for those reasons, I highly recommend that you use 1 user per site and that is really easy to do.
adduser redstonecube
You can leave most information about the user blank.
Now, to make your user easier to use, it's a good idea to customize the bashrc to make you login directly inside public_html when you enter the user. To do so, enter the user:
su youruser
And then, edit the file .bashrc after entering at your home folder:
cd ~
mkdir public_html
nano .bashrc
Now, on the very top of the file, paste this line:
cd ~/public_html
Now, if you leave your user with "exit" and enter on it again you will be pleased to see that it is on the correct location by default:
Restarting services
Now, before using the user, wp cli and other cool things, you must restart the services. This must be done on the root user:
systemctl restart php8.1-fpm
systemctl restart nginx
systemctl restart redis-server
Installing WordPress
All the hard work will pay off now. This installation will be really easy. Let's do it with WP CLI. Inside your public_html, run:
wp core download
Now, let's create our wp-config.php and then setup our WP:
wp config create --dbname=DBNAME --dbuser=DBUSER --dbpass="DBPASS"
Perfect, next thing, let's setup the actual site (site name, etc):
wp core install --url=https://yoursiteurl.com --title="Your site title" --admin_name=youradminuser --admin_password="yourpass" --admin_email="youremail"
Now it's all good except for the SSL. Let's go back to the root user to setup our beautiful SSL.
Installing and forcing the SSL
Inside our root user, let's run:
certbot
You should see the setup screen, which is really easy to follow. Just input your email, choose the options and certbot will detect your nginx site automatically:
After that, it should be up and running perfectly.
Automatic SSL renewal
A pretty good thing to do is to renew your certificate automatically. To do so, you can just use this command to create a cron job:
crontab -e
Select nano as a option and then put the following cron job:
0 0 * * 0 certbot renew
Save the file, and exit. Your SSL is good to go now and will be automatically renewed, all for 0$/month.
Finishing Redis
Redis is installed at the server side, but it's not enabled at our WordPress installation.
Before going back to your WP user, make sure to add your intended user to the redis group and to add write and read permissions to the sock file:
usermod -aG redis youruser
chmod g+rw /var/run/redis/redis-server.sock
To enable it, switch back to your user:
su youruser
And now, inside public_html, set the Redis database number to 0 and install the plugin to control it:
wp config set WP_REDIS_DATABASE 0;
wp config set WP_REDIS_PATH '/var/run/redis/redis-server.sock'
wp config set WP_REDIS_SCHEME unix
wp plugin install redis-cache --activate
wp redis enable
wp redis update-dropin
Your Redis is good to go now. You should have a smooth wp-admin experience:
Conclusion
There is still a lot that you can do after installing this setup. I'do recommend you to look on my list 7 things that you should do after installing WordPress
You can also install caching plugins to optimize your assets and start to build your business. I hope you have fun. This setup is really solid and fast. GZIP is enabled by default for HTML files, but you can easily add extra rules to nginx in order to. If you have any doubts or questions, drop a comment and I'll try to help as best as I can.
If you enjoyed this article, you can share it with your friends or subscribe to The Self Hosting Art. Thank you for reading :)
You can also help with XMR(Monero):
8AWKRGyqQ6fdaLwGVAdVTbEP6ZttSXwcYWQWy7gnq6zceTngtJgaAr82Hxr2FY5bkCUJVerccH9XNFX1qWnZxuGYTU5bJ34