EL9 - Mariadb & Nginx & PHP-FPM

Mariadb

Install (EL9 has Mariadb 10.5 version):

# dnf install -y mariadb-server
# vim /etc/my.cnf.d/mariadb-server.cnf

[mysqld]
...
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

innodb_file_format = Barracuda
innodb_file_per_table = on
innodb_default_row_format = dynamic
innodb_large_prefix = 1

# systemctl start mariadb.service && systemctl enable mariadb.service && systemctl status mariadb.service

# mysql_secure_installation
Enter current password for root (enter for none): <Enter>
Switch to unix_socket authentication [Y/n] <Enter>
Set root password? [Y/n] <Enter>
New password: <your_new_root_password>
Re-enter new password: <your_new_root_password>
Remove anonymous users? [Y/n] <Enter>
Disallow root login remotely? [Y/n] <Enter>
Remove test database and access to it? [Y/n] <Enter>
Reload privilege tables now? [Y/n] <Enter>

Note: root user has by default password-less access via socket, can be verified with:

# mysql -u root

> SHOW GRANTS FOR 'root'@'localhost';

Nginx

# dnf install -y nginx

# systemctl start nginx && systemctl enable nginx && systemctl status nginx

# vim /etc/nginx/nginx.conf

# vim /etc/nginx/nginx.conf
http {
...
        #if we want to support big image uploads, match php.ini ....
        client_max_body_size 10M;
        #it's good to put this default here
        index index.html index.htm index.php;
        #enable caching, most important lines are gzip on; and gzip_types;
        gzip on;
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
        gzip_disable "msie6";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;

        server {
                # we might want to specify other default homepage of server so we dont give attacker any info
                #root         /usr/share/nginx/html;
                root         /var/www/html;
                
                location / {
                    index  index.html index.htm;
                }
        }
}

# mkdir -p /var/www/html
# echo "cd" > /var/www/html/index.html

# systemctl restart nginx && systemctl status nginx

Check in browser: http://<ip_address>

Virtual website / user

# useradd -m example
# passwd example
# usermod -a -G nginx example
# mkdir /home/example/html
# echo "<?php phpinfo(); ?>" > /home/example/html/index.php
# chown example.example -R /home/example/html
# chmod 755 /home/example

PHP

Install epel:

# dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
# rpm -qa | grep epel

Install remi:

# dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
# rpm -qa | grep remi

Make sure no php is installed:

# yum list installed | grep php
# yum remove --noautoremove php php-common ...

Switch php module to remi and install:

# dnf module list php
# dnf module reset php
# dnf module enable php:remi-7.4
# dnf install php php-fpm php-mysqlnd php-cli php-xml php-gd php-pecl-zip php-opcache php-intl php-process php-mbstring php-bcmath php-pecl-ds php-json php-gmp
# php -v

Set up php-fpm ...

# vim /etc/php.d/50-custom.ini

;optional tweak - performance in symfony app
realpath_cache_ttl = 600
;optional tweak - lot of times we need more memory
memory_limit = 256M
;optional tweak - error reporting adjust
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE
;optional tweak
post_max_size = 12M
;optional tweak
upload_max_filesize = 10M
;set default PHP timezone
date.timezone = America/New_York
;for typical symfony app, default number is low
opcache.max_accelerated_files=20000

# cp /etc/php-fpm.d/www.conf /etc/php-fpm.d/www.conf.default
# mv /etc/php-fpm.d/www.conf /etc/php-fpm.d/example.conf
# vim /etc/php-fpm.d/example.conf

[example]
user = example
group = example
listen = 127.0.0.1:9001
listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
slowlog = /var/log/php-fpm/example-slow.log
php_admin_value[error_log] = /var/log/php-fpm/example-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/example/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/example/wsdlcache

# mkdir -p /var/lib/php/example/session
# mkdir -p /var/lib/php/example/wsdlcache
# chown example:example -R /var/lib/php/example
# systemctl restart php-fpm && systemctl enable php-fpm && systemctl status php-fpm

Create nginx virtual host:

# vim /etc/nginx/conf.d/example.conf

server {
        server_name example.com;
        root /home/example/html;

        location / {
                index index.html index.htm index.php;
        }

        location ~ \.php$ {
                include /etc/nginx/fastcgi_params;
                fastcgi_pass 127.0.0.1:9001;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        }
}

Restart and check:

# systemctl restart nginx

Check

Composer

To install composer globally follow instructions here: https://getcomposer.org/download/ and then run:

# mv composer.phar /usr/local/bin/composer

SSL (HTTPS) - acme.sh

Install acme.sh tool: https://github.com/Neilpang/acme.sh

# cd
# curl https://get.acme.sh | sh
# CTRL+D and reopen terminal

Set letsencrypt as default issuer:

# acme.sh --set-default-ca --server letsencrypt

Issue certificate using HTTP

# acme.sh --issue -d example.com -w /home/example/prod/current/sf/public

Issue certificate using DNS

Cloudflare API token (https://github.com/acmesh-official/acme.sh/wiki/dnsapi):
Cloudflare -> My profile -> API Tokens -> Create token -> Custom -> Permissions: Zone.Zone Read, Zone.DNS Edit
CF_Account_ID from sidebar on overview page.
CF_Zone_ID from sidebar on zone overview page.

# vim ~/.acme.sh/.example_cf_dns_token

# either CF_Account_ID or CF_Zone_ID is sufficient
export CF_Account_ID="xxxxxxxxxxxxxxxx"
export CF_Zone_ID="yyyyyyyyyyyyyyyy"
export CF_Token="zzzzzzzzzzzzzzzzz"

# source ~/.acme.sh/.example_cf_dns_token; acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

# crontab -e

Update acme.sh cron entry to following:

13 7 * * * /usr/bin/bash -c 'source /root/.acme.sh/.example_cf_dns_token; "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null 2>&1'

Install certificate

# acme.sh --install-cert -d example.com --key-file /etc/pki/tls/private/example.com.key --fullchain-file /etc/pki/tls/certs/example.com.fullchain.crt --reloadcmd "systemctl reload nginx"

Nginx generate strong DHE parameter:

# cd /etc/ssl/certs
# openssl dhparam -out dhparam.pem 4096

Nginx config:

server {
    listen              443 ssl;
    ...

    ssl_certificate     /etc/pki/tls/certs/example.com.fullchain.crt;
    ssl_certificate_key /etc/pki/tls/private/example.com.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    ...
}

Add redirect for notsecure version:

server {
    listen     80;
    server_name  example.com www.example.com;
    access_log  off;
    error_log   off;
    root   /path/to/webroot;
    location /.well-known/acme-challenge/ {
    }
    location / {
        return 301 https://$server_name$request_uri;
    }
}

Test website/server on https://www.ssllabs.com/ssltest/index.html (should be A rating) or https://ssldecoder.org/

Nginx reverse proxy pass-thru

Taken from: https://serversforhackers.com/c/tcp-load-balancing-with-nginx-ssl-pass-thru