Lewati ke isi

Instalasi Nginx-Joomla pada Linux

Persiapan

  1. DB Server
  2. Web server
  3. PHP

Proses

Instalasi - Joomla

Variabel
FQDN_INSTALASI="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="db_name"
DB_USER="db_user"
DB_PASSWORD="db_password"
DB_HOST="10.20.30.11"
DIREKTORI_TUJUAN="/home/deploy/$FQDN_INSTALASI"
LOKASI_PHP=$(which php)

Unduh & ekstrak Joomla

VERSI_JOOMLA="3-9-16"
wget -o joomla.zip https://downloads.joomla.org/cms/joomla3/$VERSI_JOOMLA/Joomla_$VERSI_JOOMLA-Stable-Full_Package.zip?format=zip
mkdir -v -p $DIREKTORI_WWW $DIREKTORI_CACHE $DIREKTORI_CONFIG $DIREKTORI_LOG $DIREKTORI_TMP
unzip joomla.zip -d $DIREKTORI_WWW

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 - Akses direktori session PHP

/var/lib/php/session

chown -Rv :nginx /var/lib/php/session

Konfigurasi - Nginx

Berkas-berkas *.conf

cp /etc/nginx/nginx.conf{,.`date +%Y%m%d%H%M`}
cat << EOF > /etc/nginx/nginx.conf
# Brotli
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

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.html index.htm default.html default.htm;;

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

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

    # handle .php
    location ~ \.php\$ {
        fastcgi_param       SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        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;
    #server_name_in_redirect off;
    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_http_version 1.1;
#gzip_comp_level   6;
#gzip_min_length   1100;
#gzip_buffers      4 8k;
gzip_vary         on;
gzip_proxied      any;
gzip_comp_level   6;
#gzip_types        text/plain application/xhtml+xml text/css application/xml application/xml+rss text/javascript application/javascript application/x-javascript
gzip_types        text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;

# brotli
brotli            on;
brotli_comp_level 6;
brotli_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/joomla.conf

# Joomla: deny general stuff
location ~* ^/(?:configuration\.php|htaccess\.txt|LICENSE\.txt|README\.txt|robot\.txt|web\.config\.txt)$ {
    deny all;
}

# 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;
}

# Support Clean (aka Search Engine Friendly) URLs
#location / {
#        try_files \$uri \$uri/ /index.php?\$args;
#}

# deny running scripts inside writable directories
location ~* /(images|cache|media|logs|tmp)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
        return 403;
        error_page 403 /403_error.html;
}

# caching of files 
location ~* \.(ico|pdf|flv)$ {
        expires 1y;
}

location ~* \.(js|css|png|jpg|jpeg|gif|swf|xml|txt)$ {
        expires 14d;
}
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

Konfigurasi - PHP-FPM

Meningkatkan kapasitas konsumsi memori PHP

grep -E "post_max_size|upload_max_filesize|session.save_path" /etc/php.ini
POST_MAX_SIZE="32M"
UPLOAD_MAX_FILESIZE="8"
FQDN_INSTALASI="domain.tld"
DIREKTORI_INSTALASI="/home/$FQDN_INSTALASI"
DIREKTORI_CACHE="$DIREKTORI_INSTALASI/cache"
DIREKTORI_TMP="$DIREKTORI_INSTALASI/tmp"
cp -v /etc/php.ini{,.`date +%Y%m%d%H%M`}
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
sed -i -r "s#^;session.save_path =.*#session.save_path = "$DIREKTORI_TMP"#g" /etc/php.ini
systemctl restart php-fpm

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

SELinux - Joomla

SELinux - policy package

POLICY_PACKAGE="nginx-php-fpm-joomla"
cd $DIREKTORI_PENGGUNA
cat << EOF > $POLICY_PACKAGE.te
module $POLICY_PACKAGE 1.0;

require {
    type httpd_user_htaccess_t;
    type httpd_t;
    class file { setattr write };
}

#============= httpd_t ==============
allow httpd_t httpd_user_htaccess_t:file { setattr write };
EOF
# 
semodule -r $POLICY_PACKAGE
rm -f $POLICY_PACKAGE.mod $POLICY_PACKAGE.pp
checkmodule -M -m -o $POLICY_PACKAGE.mod $POLICY_PACKAGE.te
semodule_package -o $POLICY_PACKAGE.pp -m $POLICY_PACKAGE.mod
semodule -i $POLICY_PACKAGE.pp

Sinkronisasi - pemindahan dokumen www root

rsync dari peladen lama
# Variabel
PENGGUNA_TUJUAN="pengguna_tujuan"
IP_TUJUAN="10.20.30.40"
KUNCI_PRIVAT_SSH="/home/deploy/.ssh/kunci_privat_ssh"
DIREKTORI_SUMBER="/var/www/html"
DIREKTORI_TUJUAN="/home/$PENGGUNA_TUJUAN/domain.tld"
# 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

configuration.php

cp -aRv $DIREKTORI_WWW/configuration.php{,.`date +%Y%m%d%H%M`} 
sed -i -r "s#^public \$db.*#public \$db = '$DB_NAME';#g" $DIREKTORI_WWW/configuration.php
grep -E 'a|b|c' $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|Joomla|php-fpm'
# gunakan sementara
semanage permissive -a httpd_t
# hapus jika sudah selesai
semanage permissive -d httpd_t
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
Joomla - Direktori sementara
cat << EOF >> $DIREKTORI_WWW/configuration.php

// `date +%Y-%m-%d\ %H.%M` [AL]: Lokasi direktori sementara
define('WP_TEMP_DIR', '$DIREKTORI_TMP');
EOF
systemctl restart php-fpm
Ekstensi - ???