On Centos 7, standard is mariadb 5.5. If you want stable mariadb (currently 10.4), add yum repo:
# vim /etc/yum.repos.d/MariaDB.repo # MariaDB 10.4 CentOS repository list - as of 2020-02-16 # http://downloads.mariadb.org/mariadb/repositories/ [mariadb] name = MariaDB baseurl = http://yum.mariadb.org/10.4/centos7-amd64 gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB gpgcheck=1
Install:
# yum -y install mariadb mariadb-server # vim /etc/my.cnf.d/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 # mysql_secure_installation Enter current password for root (enter for none): <Enter> ---> only for newer mariadb (10.4): Switch to unix_socket authentication [Y/n] n 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> # systemctl status mariadb.service
# yum -y install nginx # 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 # vim /var/www/html/index.html hello # systemctl restart nginx.service # systemctl status nginx.service # systemctl enable nginx.service
Make sure http protocol is unblocked in firewall.
Check by loading http://<ip_address> in your browser, it should display our "hello" page.
# 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
Make sure you have installed remi repo php72.
# yum -y 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 # cp /etc/php.ini /etc/php.ini.original # vim /etc/php.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 # vim /etc/php.d/10-opcache.ini ;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 [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 /var/lib/php/example # mkdir /var/lib/php/example/session # mkdir /var/lib/php/examplewsdlcache # chown example.example -R /var/lib/php/example # systemctl restart php-fpm.service # systemctl enable php-fpm.service # systemctl status php-fpm.service
Creating standard 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; } }
Creating Wordpress nginx virtual host:
# vim /etc/nginx/fastcgi.conf fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param REDIRECT_STATUS 200; # vim /etc/nginx/conf.d/example.conf server { listen 80; server_name example.com www.example.com; root /home/example/public_html; 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$ { include fastcgi.conf; fastcgi_intercept_errors on; fastcgi_pass 127.0.0.1:9001; } location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { expires max; log_not_found off; } error_log /var/log/nginx/example.com-error.log; access_log /var/log/nginx/example.com-access.log; }
Creating Symfony nginx virtual host:
# vim /etc/nginx/conf.d/example.conf server { listen 80; server_name example.com www.example.com; root /home/example/prod/sf/web; location / { try_files $uri /app.php$is_args$args; } location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { expires 30d; add_header Vary Accept-Encoding; access_log off; log_not_found off; } location ~ ^/(app|config)\.php(/|$) { fastcgi_pass 127.0.0.1:9001; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; # When you are using symlinks to link the document root to the # current version of your application, you should pass the real # application path instead of the path to the symlink to PHP # FPM. # Otherwise, PHP's OPcache may not properly detect changes to # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 # for more information). fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; } error_log /var/log/nginx/example.com-error.log; access_log /var/log/nginx/example.com-access.log; } server { listen 80; server_name dev.example.com www.dev.example.com; root /home/example/dev/sf/web; location / { try_files $uri /app_dev.php$is_args$args; } location ~ ^/(app_dev|config)\.php(/|$) { fastcgi_pass 127.0.0.1:9001; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; } error_log /var/log/nginx/dev.example.com-error.log; access_log /var/log/nginx/dev.example.com-access.log; }
Restarting nginx and checking:
# systemctl restart nginx
Make sure example.com points to our new server ip address (e.g. modifying /etc/hosts file)
If we put http://example.com in our browser it should display phpinfo page.
# htpasswd -c /home/example/.htpasswd username <password><enter> <password><enter> # vim /etc/nginx/conf.d/example.conf server { ... auth_basic "Restricted"; auth_basic_user_file /home/example/.htpasswd; }
To install composer globally follow instructions here:
https://getcomposer.org/download/
and then run:
# mv composer.phar /usr/local/bin/composer
# cd # curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo # curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash - # yum install -y nodejs yarn
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.
# export CF_Token="xxxxxxxxxxxxxxxx" # export CF_Account_ID="yyyyyyyyyyyyyyyy" # acme.sh --issue --dns dns_cf -d example.com -d www.example.com
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/
To add ability to rotate nginx and php-fpm for user example:
# vim /etc/sudoers ... example ALL=(root) NOPASSWD: /usr/bin/systemctl reload php-fpm example ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
Make sure nginx, php-fpm and mariadb users have raised limits:
Old settings (not used anymore):
# vim /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536 # reboot
New settings for all services run from systemd:
# vim /etc/systemd/system/nginx.service.d/override.conf [Service] LimitNOFILE=65536 # vim /etc/systemd/system/php-fpm.service.d/override.conf [Service] LimitNOFILE=65536 # systemctl daemon-reload
Update nginx max open files configuration for workers:
# vim /etc/nginx/nginx.conf #worker_processes auto; #number of processor cores: cat /proc/cpuinfo |grep processor worker_processes 8; # to prevent error: socket() failed (24: Too many open files) while connecting to upstream # must be smaller than total global limit for nginx worker_rlimit_nofile 30000; # systemctl restart nginx # systemctl restart php-fpm
E.g. to find out actual Nginx limits, ps aux | grep nginx to locate nginx’s process IDs. Then query each process’s file handle limits using cat /proc/pid/limits
http://www.tweaked.io/guide/kernel/
https://wiki.mikejung.biz/Sysctl_tweaks
https://blog.cloudflare.com/syn-packet-handling-in-the-wild/
# vim /etc/sysctl.d/99-sysctl.conf # by default, OS uses only 28k ports (32768-60999) for sockets, let's utilize more # make sure we dont have any service running on port above the min limit net.ipv4.ip_local_port_range = 16384 64999 # dealing with burst traffic # if all sockets are listening and busy a connection-backlog will pile up # let's increase the size of the backlog (if backlog is full, connection will fail - will be refused) # The maximum number of "backlogged sockets". Default is 128. net.core.somaxconn = 32768 # Increase size of uncomplete connections backlog (4k is safe value on systems with few gigs of memory) net.ipv4.tcp_max_syn_backlog = 4096 # Increase size of established connections backlog (4k is safe value on systems with few gigs of memory) net.core.netdev_max_backlog = 4096 # 16MB per socket net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 # #net.ipv4.tcp_wmem = 4096 65536 16777216 #net.ipv4.tcp_rmem = 4096 87380 16777216 # let's lower limit for SYN and SYNACK retries during establishing connection # this might drop very slow clients - defaults are ~5 #net.ipv4.tcp_syn_retries = 2 #net.ipv4.tcp_synack_retries = 2
Apply changes:
# sysctl -p
Use https://github.com/major/MySQLTuner-perl
Following config is for dedicated db machine with 16GB RAM
# vim /etc/my.cnf [mysqld] ... #all user's hosts must be defined with ip address skip-name-resolve thread_cache_size = 4 query_cache_type = 1 query_cache_limit = 1M query_cache_min_res_unit = 2k query_cache_size = 300M innodb_file_per_table = 1 innodb_buffer_pool_size = 8G #on dedicated db machine with innodb db should be 65%-75% of system memory innodb_buffer_pool_instances = 8 #recommended by mysqltuner innodb_log_file_size = 1G # should be ~25% of innodb_buffer_pool_size innodb_flush_log_at_trx_commit = 0 # performance optimization (default value 1 is for reliability) - use only on UPS-backed-up machine if you dont want to lose data slow_query_log = 1 long_query_time = 1 slow_query_log_file = /var/log/mariadb/slow-query.log log_queries_not_using_indexes