The idea is to start with a stable system (Debian 11) to install all the components of a fast and secure Web Server (HTTP2, TLS 1.2 et 1.3 with best practices).
Moreover, assuming that a webserver alone is useless without a website on it, we are going to install a Wordpress (because it's the most used CMS in the world at this time).
Here is what we are going to use:
Least but not last, here is the two cache system we are going to use :
We use two well-known websites for estimating security of the NGINX side, SSL Labs and Security Headers:
In this first part, we are going to install the packages needed.
It is mandatory to go throught sury.org's repository to get PHP 8.0 on Debian 11
apt-get -y install apt-transport-https lsb-release ca-certificates curl
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt-get update
Install Nginx, MariaDB, PHP 8.0 and Redis:
apt install curl git unzip imagemagick haveged mariadb-client mariadb-server nginx-extras php-common php-pear php-redis redis php-zip php8.0-cli php8.0-common php8.0-curl php8.0-dev php8.0-fpm php8.0-gd php8.0-imap php8.0-intl php8.0-mbstring php8.0-mysql php8.0-opcache php8.0-pspell php8.0-readline php8.0-snmp php8.0-tidy php8.0-xml php8.0-zip
We are going to configure all softwares one by one now.
Grab my Nginx secure config, which is using the headers-more module:
cd /etc/nginx/
rm nginx.conf && wget https://raw.githubusercontent.com/stylersnico/nginx-secure-config/master/nginx.conf-debian-extras && mv nginx.conf-debian-extras nginx.conf
You can open the config file and adapt workers depending on your cpu cores on your server.
You can also go through all security options that are integrated. We will go back to nginx after, please now restart the service:
systemctl restart nginx
Nothing special, launch the integrated utility to secure the Sql Server:
mysql_secure_installation
Now, open the config file and add this to the [mysqld] part:
nano /etc/mysql/mariadb.conf.d/50-server.cnf
#
# * Fine Tuning
#
max_connections = 50
connect_timeout = 5
wait_timeout = 600
max_allowed_packet = 16M
thread_cache_size = 128
sort_buffer_size = 4M
bulk_insert_buffer_size = 16M
tmp_table_size = 32M
max_heap_table_size = 32M
#
# * Query Cache Configuration
#
# Cache only tiny result sets, so we can fit more in the query cache.
query_cache_limit = 512K
query_cache_size = 32M
We will also connect after to create database, now please restart the service:
systemctl restart mysql
Create the FPM socket directory with this command:
mkdir -p /var/lib/php8.0-fpm/
Open the PHP-FPM config and add this (and the Timezone):
nano /etc/php/8.0/fpm/php.ini
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1
date.timezone = Europe/Paris
session.cookie_httponly = True
max_execution_time = 300
max_input_vars = 1740
post_max_size=100M
upload_max_filesize=100M
Same thing for PHP-CLI config:
nano /etc/php/8.0/cli/php.ini
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1
date.timezone = Europe/Paris
session.cookie_httponly = True
max_execution_time = 300
max_input_vars = 1740
post_max_size=100M
upload_max_filesize=100M
Restart PHP-FPM:
systemctl restart php8.0-fpm
Open the Redis config file:
nano /etc/redis/redis.conf
Edit the following to configure cache size and expiration:
maxmemory 256mb
maxmemory-policy allkeys-lru
Restart Redis:
systemctl restart redis
In this part, we are going to use the Web Server that we set up just before to install the most popular content management system, Wordpress.
Delete all the default config files:
rm /etc/nginx/sites-enabled/default
rm /etc/php/8.0/fpm/pool.d/www.conf
Download the latest release of Wordpress:
cd /var/www/
wget https://wordpress.org/latest.zip && unzip latest.zip && rm latest.zip
Create the wordpress user:
adduser wordpress
Adjust the rights on the website:
chown -R wordpress:www-data /var/www/wordpress
Add the new user in the www-data group:
adduser wordpress www-data
Open the vhost with this command:
nano /etc/nginx/sites-enabled/wordpress.vhost
Add this to the file:
server {
listen 80;
server_name website.tap.ovh;
root /var/www/wordpress/;
index index.php;
}
Create the PHP-FPM pool with this command:
nano /etc/php/8.0/fpm/pool.d/wordpress.conf
Add this:
[wordpress]
listen = /var/lib/php8.0-fpm/wordpress.sock
listen.owner = wordpress
listen.group = www-data
listen.mode = 0660
user = wordpress
group = www-data
pm = static
pm.max_children = 15
chdir = /
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Restart PHP-FPM and NGINX:
systemctl restart nginx && systemctl restart php8.0-fpm
Connect to the MariaDB server with this command:
mysql -u root -p
Create the database for wordpress:
CREATE DATABASE wordpress;
Create the user:
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'password';
Give the rights to the user:
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
Apply the rights and exit:
FLUSH PRIVILEGES;
exit
Install acme.sh:
curl https://get.acme.sh | sh -s email=test@tap.ovh
cd /root/.acme.sh/
chmod +x acme.sh
sh acme.sh --set-default-ca --server letsencrypt
Launch this command with your domain to get the certificate:
sh acme.sh --issue -d website.tap.ovh --nginx /etc/nginx/sites-enabled/wordpress.vhost --keylength ec-384
If the operation is successful, you just have to add the certificate to your nginx configuration:
[Wed 11 Aug 2021 08:21:06 PM CEST] Your cert is in: /root/.acme.sh/website.tap.ovh_ecc/website.tap.ovh.cer
[Wed 11 Aug 2021 08:21:06 PM CEST] Your cert key is in: /root/.acme.sh/website.tap.ovh_ecc/website.tap.ovh.key
[Wed 11 Aug 2021 08:21:06 PM CEST] The intermediate CA cert is in: /root/.acme.sh/website.tap.ovh_ecc/ca.cer
[Wed 11 Aug 2021 08:21:06 PM CEST] And the full chain certs is there: /root/.acme.sh/website.tap.ovh_ecc/fullchain.cer
Edit the NGINX vhost to add the certificate (fullchain and private key):
server {
listen 80;
listen 443 ssl http2;
ssl_certificate /root/.acme.sh/website.tap.ovh_ecc/fullchain.cer;
ssl_certificate_key /root/.acme.sh/website.tap.ovh_ecc/website.tap.ovh.key;
if ($scheme != "https") {
rewrite ^ https://$http_host$request_uri? permanent;
}
server_name website.tap.ovh;
root /var/www/wordpress/;
location /.well-known/acme-challenge {
alias /var/www/wordpress/.well-known/acme-challenge/;
}
index index.php;
location = /xmlrpc.php {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ \.php$ {
try_files /e1d4ea2d073f20faebaf9539ddde872c.htm @php;
}
location @php {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/lib/php8.0-fpm/wordpress.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
}
location ~ ^/(status|ping)$ {
access_log off;
deny all;
}
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|css|js|woff|woff2|webp)$ {
expires max;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
location / {
try_files $uri $uri/ /index.php?$args;
}
}
Restart Nginx and install your blog :
Right after wordpress installation, add those two lines to your Wordpress config:
nano /var/www/wordpress/wp-config.php
define('WP_CACHE', true);
define('WP_CACHE_KEY_SALT', 'website.tap.ovh');
Then, download and activate Redis Object Cache.
When it's done, check the Redis object cache with this command :
redis-cli monitor
If you want to go forward, you could add WEBP images to your website (FRENCH, translation waiting): https://www.abyssproject.net/2020/05/mettre-en-place-les-images-au-format-webp-sur-son-site-avec-nginx/