Lewati ke isi

Instalasi Nginx-Wordpress pada Linux

Persiapan

  1. DB Server
  2. Web server
  3. PHP

Proses

Instalasi - Wordpress

Variabel
FQDN_INSTALASI="sub.domain.tld"
PENGGUNA="nginx"
SUREL_LE="[email protected]"
DIREKTORI_INSTALASI="/home/$FQDN_INSTALASI"
DIREKTORI_CACHE="$DIREKTORI_INSTALASI/cache"
DIREKTORI_CONFIG="$DIREKTORI_INSTALASI/config"
DIREKTORI_LOG="$DIREKTORI_INSTALASI/log"
DIREKTORI_TMP="$DIREKTORI_INSTALASI/tmp"
DIREKTORI_WWW="$DIREKTORI_INSTALASI/www"
DB_NAME="skema_pangkalan_data"
DB_USER="pengguna_pangkalan_data"
DB_PASSWORD="kata sandi kuat sekali luar biasa"
DB_HOST="10.20.30.40"
DIREKTORI_TUJUAN="/lokasi/direktori/tujuan"
LOKASI_PHP=$(which php)

Unduh & ekstrak Wordpress

wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
mkdir -v -p $DIREKTORI_WWW $DIREKTORI_CACHE $DIREKTORI_CONFIG $DIREKTORI_LOG $DIREKTORI_TMP
mv -v wordpress/* $DIREKTORI_WWW
rm -v -rf latest.tar.gz wordpress

SELinux - setiap folder yang baru dibuat

semanage fcontext -a -t httpd_cache_t "$DIREKTORI_CACHE(/.*)?"
semanage fcontext -a -t httpd_config_t "$DIREKTORI_CONFIG(/.*)?"
semanage fcontext -a -t httpd_log_t "$DIREKTORI_LOG(/.*)?"
semanage fcontext -a -t httpd_tmp_t "$DIREKTORI_TMP(/.*)?"
semanage fcontext -a -t httpd_sys_content_t "$DIREKTORI_WWW(/.*)?"
semanage fcontext -a -t httpd_sys_rw_content_t "$DIREKTORI_WWW/wp-content(/.*)?"
semanage fcontext -a -t httpd_sys_script_exec_t "$DIREKTORI_WWW/wp-includes(/.*)?"
systemctl restart nginx php-fpm
restorecon -rv $DIREKTORI_CACHE
restorecon -rv $DIREKTORI_CONFIG
restorecon -rv $DIREKTORI_LOG
restorecon -rv $DIREKTORI_TMP
restorecon -rv $DIREKTORI_WWW
restorecon -rv $DIREKTORI_WWW/wp-content
restorecon -rv $DIREKTORI_WWW/wp-includes
systemctl restart nginx php-fpm
Memastikan pengaturan hak akses folder dan berkas
chown -Rv $PENGGUNA: $DIREKTORI_INSTALASI
find $DIREKTORI_INSTALASI -type d -exec chmod -v 0755 {} \;
find $DIREKTORI_INSTALASI -type f -exec chmod -v 0644 {} \;

Konfigurasi - Diffie-Hellman

Membuat kunci Diffie-Hellman

openssl dhparam -out /etc/nginx/dhparam.pem 2048
mkdir -v -p /var/www/_letsencrypt
chown -Rv nginx /var/www/_letsencrypt

Konfigurasi - Nginx

Berkas-berkas *.conf

cp /etc/nginx/nginx.conf{,.`date +%Y%m%d%H%M`}
cat << EOF > /etc/nginx/nginx.conf
user                 nginx;
pid                  /var/run/nginx.pid;
worker_processes     auto;
worker_rlimit_nofile 65535;

events {
    multi_accept       on;
    worker_connections 65535;
}

http {
    charset              utf-8;
    sendfile             on;
    tcp_nopush           on;
    tcp_nodelay          on;
    server_tokens        off;
    types_hash_max_size  2048;
    client_max_body_size 16M;

    # MIME
    include              mime.types;
    default_type         application/octet-stream;

    # Logging
    access_log           /var/log/nginx/access.log;
    error_log            /var/log/nginx/error.log warn;

    # Limits
    limit_req_log_level  warn;
    limit_req_zone       \$binary_remote_addr zone=login:10m rate=10r/m;

    # SSL
    ssl_session_timeout  1d;
    ssl_session_cache    shared:SSL:10m;
    ssl_session_tickets  off;

    # Diffie-Hellman parameter for DHE ciphersuites
    ssl_dhparam          /etc/nginx/dhparam.pem;

    # Mozilla Intermediate configuration
    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_ciphers          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # OCSP Stapling
    ssl_stapling         on;
    ssl_stapling_verify  on;
    resolver             1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    resolver_timeout     2s;

    # Load configs
    include              /etc/nginx/conf.d/*.conf;
    include              /etc/nginx/sites-enabled/*;
}
EOF
cat << EOF > $DIREKTORI_CONFIG/$FQDN_INSTALASI.conf
server {
    listen                  443 ssl http2;
    listen                  [::]:443 ssl http2;
    server_name             $FQDN_INSTALASI;
    set                     \$base /home/$FQDN_INSTALASI;
    root                    \$base/www;

    # SSL
    ssl_certificate         /etc/letsencrypt/live/$FQDN_INSTALASI/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/$FQDN_INSTALASI/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/$FQDN_INSTALASI/chain.pem;

    # security
    include                 $DIREKTORI_CONFIG/security.conf;

    # logging
    access_log              $DIREKTORI_LOG/$FQDN_INSTALASI.access.log;
    error_log               $DIREKTORI_LOG/$FQDN_INSTALASI.error.log warn;

    # index.php
    index                   index.php;

    # index.php fallback
    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    # additional config
    include $DIREKTORI_CONFIG/general.conf;
    include $DIREKTORI_CONFIG/wordpress.conf;

    # handle .php
    location ~ \.php\$ {
        include $DIREKTORI_CONFIG/php_fastcgi.conf;
    }
}

# subdomains redirect
server {
    listen                  443 ssl http2;
    listen                  [::]:443 ssl http2;
    server_name             *.$FQDN_INSTALASI;

    # SSL
    ssl_certificate         /etc/letsencrypt/live/$FQDN_INSTALASI/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/$FQDN_INSTALASI/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/$FQDN_INSTALASI/chain.pem;
    return                  301 https://$FQDN_INSTALASI\$request_uri;
}

# HTTP redirect
server {
    listen      80;
    listen      [::]:80;
    server_name .$FQDN_INSTALASI;
    include     $DIREKTORI_CONFIG/letsencrypt.conf;

    location / {
        return 301 https://$FQDN_INSTALASI\$request_uri;
    }
}
EOF
cat << EOF > $DIREKTORI_CONFIG/letsencrypt.conf
# ACME-challenge
location ^~ /.well-known/acme-challenge/ {
    root /var/www/_letsencrypt;
}
EOF
cat << EOF > $DIREKTORI_CONFIG/security.conf
# security headers
add_header X-Frame-Options           "SAMEORIGIN" always;
add_header X-XSS-Protection          "1; mode=block" always;
add_header X-Content-Type-Options    "nosniff" always;
add_header Referrer-Policy           "no-referrer-when-downgrade" always;
add_header Content-Security-Policy   "default-src 'self' http: https: data: blob: 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# . files
location ~ /\.(?!well-known) {
    deny all;
}
EOF
cat << EOF > $DIREKTORI_CONFIG/general.conf
# favicon.ico
location = /favicon.ico {
    log_not_found off;
    access_log    off;
}

# robots.txt
location = /robots.txt {
    log_not_found off;
    access_log    off;
}

# assets, media
location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {
    expires    7d;
    access_log off;
}

# svg, fonts
location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {
    add_header Access-Control-Allow-Origin "*";
    expires    7d;
    access_log off;
}

# gzip
gzip              on;
gzip_vary         on;
gzip_proxied      any;
gzip_comp_level   6;
gzip_types        text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
EOF
cat << EOF > $DIREKTORI_CONFIG/wordpress.conf
# WordPress: allow TinyMCE
location = /wp-includes/js/tinymce/wp-tinymce.php {
    include $DIREKTORI_CONFIG/php_fastcgi.conf;
}

# WordPress: deny wp-content, wp-includes php files
location ~* ^/(?:wp-content|wp-includes)/.*\.php$ {
    deny all;
}

# WordPress: deny wp-content/uploads nasty stuff
location ~* ^/wp-content/uploads/.*\.(?:s?html?|php|js|swf)$ {
    deny all;
}

# WordPress: SEO plugin
location ~* ^/wp-content/plugins/wordpress-seo(?:-premium)?/css/main-sitemap\.xsl$ {}

# WordPress: deny wp-content/plugins (except earlier rules)
location ~ ^/wp-content/plugins {
    deny all;
}

# WordPress: deny scripts and styles concat
location ~* \/wp-admin\/load-(?:scripts|styles)\.php {
    deny all;
}

# WordPress: deny general stuff
location ~* ^/(?:xmlrpc\.php|wp-links-opml\.php|wp-config\.php|wp-config-sample\.php|wp-comments-post\.php|readme\.html|license\.txt)$ {
    deny all;
}

# WordPress: throttle wp-login.php
location = /wp-login.php {
    limit_req zone=login burst=2 nodelay;
    include   $DIREKTORI_CONFIG/php_fastcgi.conf;
}

# WordPress: Wordfence plugin
location ~ ^/\.user\.ini {
    deny all;
}

# WordPress: iThemes Security plugin
location ~ ^/nginx\.conf {
    deny all;
}
EOF
cat << EOF > $DIREKTORI_CONFIG/php_fastcgi.conf
# 404
try_files                     \$fastcgi_script_name =404;

# default fastcgi_params
include                       fastcgi_params;

# fastcgi settings
fastcgi_pass                  unix:/var/run/php-fpm/www.sock;
fastcgi_index                 index.php;
fastcgi_buffers               8 16k;
fastcgi_buffer_size           32k;

# fastcgi params
fastcgi_param DOCUMENT_ROOT   \$realpath_root;
fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;
fastcgi_param PHP_ADMIN_VALUE "open_basedir=\$base/:/usr/lib/php/:/tmp/";
EOF

Konfigurasi - logrotate

Mengatur agar log $FQDN dapat otomatis melakukan rotasi dan pengarsipan harian

cat << EOF >> /etc/logrotate.d/nginx

$DIREKTORI_LOG/*.log {
        daily
        missingok
        rotate 52
        compress
        delaycompress
        notifempty
        create 640 nginx adm
        sharedscripts
        postrotate
                if [ -f /var/run/nginx.pid ]; then
                        kill -USR1 `cat /var/run/nginx.pid`
                fi
        endscript
}
EOF

Instalasi - Certbot

Instalasi persyaratan & menonaktifkan sementara konfigurasi SSL

dnf install -y certbot python3-certbot-nginx
sed -i -r 's/(listen .*443)/\1;#/g; s/(ssl_(certificate|certificate_key|trusted_certificate) )/#;#\1/g' \
    $DIREKTORI_CONFIG/$FQDN_INSTALASI.conf
ln -v -s $DIREKTORI_CONFIG/$FQDN_INSTALASI.conf /etc/nginx/conf.d/
nginx -t && systemctl reload nginx

Mendapatkan SSL & mengaktifkan kembali konfigurasi SSL

Lakukan perubahan alamat IP sebelum menjalankan perintah untuk mendapatkan sertifikat SSL

certbot certonly --webroot -d $FQDN_INSTALASI --email $SUREL_LE -w /var/www/_letsencrypt \
    -n --agree-tos --force-renewal
sed -i -r 's/#?;#//g' $DIREKTORI_CONFIG/$FQDN_INSTALASI.conf
nginx -t && systemctl reload nginx

Mengatur Certbot agar memuat ulang Nginx saat setelah sukses pembaruan SSL

echo -e '#!/bin/bash\nnginx -t && systemctl reload nginx' | \
    tee /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
chmod a+x /etc/letsencrypt/renewal-hooks/post/nginx-reload.sh
nginx -t && systemctl reload nginx

Penyelesaian

Sinkronisasi - pemindahan dokumen www root

rsync dari peladen lama
# Variabel
PENGGUNA_TUJUAN="deploy"
IP_TUJUAN="188.166.216.45"
KUNCI_PRIVAT_SSH="/home/deploy/.ssh/do-proxsis-wp07-iyodev-com"
DIREKTORI_SUMBER="/var/www/html"
DIREKTORI_TUJUAN="/home/$PENGGUNA_TUJUAN/iyodev"
# Sinkronisasi sumber-tujuan
chmod -v 400 $KUNCI_PRIVAT_SSH
rsync -avP \
    -e "ssh -i $KUNCI_PRIVAT_SSH" \
    $DIREKTORI_SUMBER \
    $PENGGUNA_TUJUAN@$IP_TUJUAN:$DIREKTORI_TUJUAN

Menempatkan isi direktori peladen lama ke www root peladen baru

cp -av $DIREKTORI_WWW{,.orig}
rm -rfv $DIREKTORI_WWW/*
cp -aRv $DIREKTORI_TUJUAN/* $DIREKTORI_WWW
chown -Rv $PENGGUNA: $DIREKTORI_INSTALASI

SELinux - memastikan pemulihan aturan terhadap folder & berkas

restorecon -rv $DIREKTORI_CACHE
restorecon -rv $DIREKTORI_CONFIG
restorecon -rv $DIREKTORI_LOG
restorecon -rv $DIREKTORI_TMP
restorecon -rv $DIREKTORI_WWW
restorecon -rv $DIREKTORI_WWW/wp-content
restorecon -rv $DIREKTORI_WWW/wp-includes
systemctl restart nginx php-fpm
matchpathcon -V $DIREKTORI_CACHE/*
matchpathcon -V $DIREKTORI_CONFIG/*
matchpathcon -V $DIREKTORI_LOG/*
matchpathcon -V $DIREKTORI_TMP/*
matchpathcon -V $DIREKTORI_WWW/*
matchpathcon -V $DIREKTORI_WWW/wp-content/*
matchpathcon -V $DIREKTORI_WWW/wp-includes/*
ls -ladZ $DIREKTORI_INSTALASI/*
systemctl restart nginx php-fpm

Konfigurasi - koneksi peladen pangkalan data

wp-config.php

sed -i -r "s#^define\('DB_NAME.*#define('DB_NAME', '$DB_NAME');#g" $DIREKTORI_WWW/wp-config.php
sed -i -r "s#^define\('DB_USER.*#define('DB_USER', '$DB_NAME');#g" $DIREKTORI_WWW/wp-config.php
sed -i -r "s#^define\('DB_PASSWORD.*#define('DB_PASSWORD', '$DB_PASSWORD');#g" $DIREKTORI_WWW/wp-config.php
sed -i -r "s#^define\('DB_HOST.*#define('DB_HOST', '$DB_HOST:3306');#g" $DIREKTORI_WWW/wp-config.php
# sed -i -r "s#^define\('WP_TEMP_DIR.*#define\('WP_TEMP_DIR', '$DIREKTORI_TMP');#g" \
#     $DIREKTORI_WWW/wp-config.php
# sed -i -r "s#^define\('SUCURI_DATA_STORAGE.*#define\('SUCURI_DATA_STORAGE', '$DIREKTORI_LOG');#g" \
#     $DIREKTORI_WWW/wp-config.php
grep -E 'DB_NAME|DB_USER|DB_PASSWORD|DB_HOST|WP_TEMP_DIR|SUCURI_DATA_STORAGE' $DIREKTORI_WWW/wp-config.php
mencadangkan pangkalan data
PANGKALAN_DATA="pangkalan_data"
mysqldump --single-transaction --routines --add-drop-table --extended-insert -u root -p \
    $PANGKALAN_DATA > $PANGKALAN_DATA-`date +%Y%d%m%H%M`.sql

Konfigurasi - cron

crontab -e

cat << EOF > crontab.txt
0 * * * * su nginx -s /bin/sh -c "cd $DIREKTORI_WWW/; $LOKASI_PHP -q wp-cron.php"
EOF
crontab crontab.txt

Isu & kendala

SELinux - debug
tail -F /var/log/messages | grep -E 'setroubleshoot|preventing|denied'
grep -E 'setroubleshoot|preventing' /var/log/messages
sealert -a /var/log/audit/audit.log
semanage fcontext -l | grep -E 'httpd_|nginx|wordpress|php-fpm'
# gunakan sementara
semanage permissive -a httpd_t
# hapus jika sudah selesai
semanage permissive -d httpd_t
PHP - Penyesuaian nilai parameter untuk tema Wordpress
Variabel
PHP_VER="7.4"
MAX_EXECUTION_TIME="180"
MAX_INPUT_VARS="3000"
POST_MAX_SIZE="16M"
UPLOAD_MAX_FILESIZE="16M"
cp -v /etc/php.ini{,.`date +%Y%m%d%H%M`}
sed -i -r "s#^max_execution_time =.*#max_execution_time = $MAX_EXECUTION_TIME#g" /etc/php.ini
sed -i -r "s#^;max_input_vars =.*#max_input_vars = $MAX_INPUT_VARS#g" /etc/php.ini
sed -i -r "s#^post_max_size =.*#post_max_size = $POST_MAX_SIZE#g" /etc/php.ini
sed -i -r "s#^upload_max_filesize =.*#upload_max_filesize = $UPLOAD_MAX_FILESIZE#g" /etc/php.ini
INIPATH="/etc/php/$PHPVER/fpm/php.ini"
cp -v $INIPATH{,.`date +%Y%m%d%H%M`}
sed -i -r "s#^max_execution_time =.*#max_execution_time = $MAX_EXECUTION_TIME#g" $INIPATH
sed -i -r "s#^;max_input_vars =.*#MAX_INPUT_VARS = $ZONA_WAKTU#g" $INIPATH
sed -i -r "s#^post_max_size =.*#post_max_size = $POST_MAX_SIZE#g" $INIPATH
sed -i -r "s#^upload_max_filesize =.*#upload_max_filesize = $UPLOAD_MAX_FILESIZE#g" $INIPATH
WP-Admin - CSS & Javascript tidak termuat
sed -i "/define('WP_DEBUG/ a // `date +%Y-%m-%d\ %H.%M` [AL]: WP-Admin - CSS & Javascript tidak termuat\ndefine('CONCATENATE_SCRIPTS', false);" $DIREKTORI_WWW/wp-config.php
systemctl restart php-fpm
Wordpress - Direktori sementara
cat << EOF >> $DIREKTORI_WWW/wp-config.php

// `date +%Y-%m-%d\ %H.%M` [AL]: Lokasi direktori sementara
define('WP_TEMP_DIR', '$DIREKTORI_TMP');
EOF
systemctl restart php-fpm
Plugin - Sucuri
cat << EOF >> $DIREKTORI_WWW/wp-config.php

// `date +%Y-%m-%d\ %H.%M` [AL]: Lokasi log Sucuri
define('SUCURI_DATA_STORAGE', '$DIREKTORI_LOG');
EOF
touch $DIREKTORI_LOG/sucuri-{blockedusers,failedlogins,hookdata,oldfailedlogins,plugindata,trustip}.php
chown -Rv $PENGGUNA: $DIREKTORI_LOG
systemctl restart php-fpm
Plugin - EWWW Image Optimizer
ausearch -c 'jpegtran' --raw | audit2allow -M ewww && semodule -i ewww.pp
systemctl restart nginx php-fpm
Plugin - iTheme Security
ausearch -c 'php-fpm' --raw | audit2allow -M php-fpm && semodule -i php-fpm.pp
systemctl restart nginx php-fpm
Log rotate
ausearch -c 'logrotate' --raw | audit2allow -M logrotate && semodule -i logrotate.pp
systemctl restart nginx php-fpm