Make sure the server IP is not blocked:
apt update -y && add-apt-repository -y ppa:ondrej/php
~/.ssh/config setting the user as root.root via SSH (ssh SERVER)adduser ifor
usermod -a -G sudo ifor
usermod -a -G www-data ifor
mkdir /home/ifor/.ssh
chown -R ifor:ifor /home/ifor/
cat $HOME/.ssh/id_rsa.pub | ssh SERVER "cat >> /home/ifor/.ssh/authorized_keys"~/.ssh/config)ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_deploy -C "Deploy key for karst-climber"chmod 600 ~/.ssh/id_rsa_deploychmod 700 ~/.sshvi ~/.ssh/config and insert:Host github
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_deploy
IdentitiesOnly yes
eval "$(ssh-agent -s)"ssh-add ~/.ssh/id_rsa_deployssh -T git@github.comgit clone https://github.com/iforwms/dotfiles.git $HOME/.dotfiles.dotfiles and create symlinks for config files:
ln -s /home/ifor/.dotfiles/tmux/.tmux.conf .ln -s /home/ifor/.dotfiles/.vim .ln -s /home/ifor/.dotfiles/.vimrc ./home/ifor/.dotfiles/scripts/server/secure_sshsudo apt updatesudo apt install -y zshsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"rm -f $HOME/.zshrc and replace with symlink
ln -s /home/ifor/.dotfiles/zsh/.zshrc .zsh plugins:
rm -rf $HOME/.dotfiles/zsh/plugins/zsh-autosuggestions && rm -rf $HOME/.dotfiles/zsh/plugins/zsh-syntax-highlighting && git clone --depth 1 https://github.com/zsh-users/zsh-autosuggestions $HOME/.dotfiles/zsh/plugins/zsh-autosuggestions && git clone --depth 1 https://github.com/zsh-users/zsh-syntax-highlighting.git $HOME/.dotfiles/zsh/plugins/zsh-syntax-highlighting && rm -rf $HOME/.dotfiles/zsh/plugins/zsh-vi-man && git clone --depth 1 https://github.com/TunaCuma/zsh-vi-man ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-vi-manchmod -R go-w ~/.dotfiles/zshfind ~/.dotfiles/zsh -type d -exec chmod 755 {} \;find ~/.dotfiles/zsh -type f -exec chmod 644 {} \;zsh -
chsh -s $(which zsh)vi colour scheme:
wget https://raw.githubusercontent.com/joshdick/onedark.vim/master/colors/onedark.vim -O $HOME/.vim/colors/onedark.vimwget https://raw.githubusercontent.com/joshdick/onedark.vim/master/autoload/onedark.vim -O $HOME/.vim/autoload/onedark.vimtimedatectl list-timezonessudo timedatectl set-timezone Asia/Shanghaisudo apt install -y libmaxminddb0 libmaxminddb-dev mmdb-bin build-essential libpcre3-dev zlib1g-dev libssl-dev libxml2-dev libxslt-dev libgd-devsudo apt install -y wget tree htop tmux ncdu git clamav sqlite3sudo hostnamectl set-hostname new-hostnamecurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
nvm ls-remote nvm install v24.13.0
node -v npm -v
sudo apt install -y nginxnginx has permissions to read the folder (and
containing folders of the code)conf.d to root:
sudo chown -R root:root /etc/nginxsudo chmod -R 755 /etc/nginxsudo vi /etc/nginx/nginx.conf:
# server_tokens off; and update the
gzip conf and headers as follows: add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
client_max_body_size 200M;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
sudo systemctl restart nginxsudo systemctl stop apache2sudo systemctl disable apache2sudo systemctl mask apache21. `nginx -v`
2. Create temp directory and `cd` inside
3. `wget https://nginx.org/download/nginx-1.18.0.tar.gz` (or version returned by previous command)
4. `git clone https://github.com/aperezdc/ngx-fancyindex.git`
5. Get GeoIP module: `wget https://github.com/leev/ngx_http_geoip2_module/archive/refs/tags/3.3.tar.gz`
6. `tar xvfz 3.3.tar.gz`
7. `nginx -V` to get the exact arguments `nginx` was installed with
8. `cd nginx-1.18.0`
9. `./configure [... args from nginx -V] --add-dynamic-module=../ngx-fancyindex` (replacing the GeoIP location with the newly downloaded one - `--add-dynamic-module=../ngx_http_geoip2_module-3.3 --with-stream`
10. Once `./configure` completes: `make modules` which will generate `*.so` files in the `objs` directory
11. `sudo cp -vi ./objs/ngx_http_fancyindex_module.so ./objs/ngx_http_geoip2_module.so ./objs/ngx_stream_geoip2_module.so /usr/share/nginx/modules`
14. Add `load_module /usr/share/nginx/modules/ngx_http_fancyindex_module.so;` to `/etc/nginx/nginx.conf` (after L4. `include /etc/nginx/modules-enabled/*.conf;`).
15. `sudo nginx -t` and `sudo service nginx restart`
16. Finally use `fancyindex` directive instead of `autoindex`
17. For sites that use fancy index, download the theme and copy light/dark theme to the root folder: `git clone --depth 1 git@github.com:Naereen/Nginx-Fancyindex-Theme`
18. Set `location` directive as follows:
fancyindex on;
fancyindex_localtime on;
fancyindex_exact_size off;
# Specify the path to the header.html and foother.html files, that are server-wise,
# ie served from root of the website. Remove the leading '/' otherwise.
fancyindex_header "/Nginx-Fancyindex-Theme-light/header.html";
fancyindex_footer "/Nginx-Fancyindex-Theme-light/footer.html";
# Ignored files will not show up in the directory listing, but will still be public.
fancyindex_ignore "examplefile.html";
# Making sure folder where these files are do not show up in the listing.
fancyindex_ignore "Nginx-Fancyindex-Theme-light";
For MySQL to run, the droplet must have at least 1GB of RAM.
sudo apt install -y mysql-server if it fails:
sudo apt purge 'mysql*'sudo apt autoremovesudo apt autocleansudo mysql_secure_installation
sudo mysql -u rootALTER USER 'root'@'localhost' IDENTIFIED BY 'NEW_PASS'; flush privileges; exit;sudo mysql -u root -p
<new-password>mysql --help | grep my.cnfsudo mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql -u root -p mysqlsudo vi /etc/mysql/mysql.conf.d/mysqld.cnfdefault-time-zone='+08:00' or
default-time-zone='Europe/Stockholm' under
[mysqld] sectionsudo systemctl restart mysqlsudo mkdir -p /var/run/mysqldsudo chown mysql:mysql /var/run/mysqldsudo systemctl stop mysqlsudo mysqld_safe --skip-grant-tables --skip-networking &UPDATE mysql.user SET authentication_string=null WHERE User='root';update user set plugin="mysql_native_password" where User='root';FLUSH PRIVILEGES;exit;sudo apt install -y php php-{fpm,mysql,xml,common,cli,mbstring,curl,zip,json,bcmath,tokenizer,gd,imagick,intl}sudo apt install -y curl gpg gnupg2 software-properties-common ca-certificates apt-transport-https lsb-releasesudo add-apt-repository -y ppa:ondrej/phpsudo add-apt-repository -y ppa:ondrej/nginxsudo apt update -ysudo apt -y install php8.4 php-{fpm,mysql,xml,common,cli,mbstring,curl,zip,bcmath,tokenizer,gd,imagick,intl,sqlite3}dig +short launchpad.net A
# Example result:
# 185.125.189.223
# Temporarily force IPv4
sudo bash -c 'echo "185.125.189.223 launchpad.net" >> /etc/hosts'
# Then run
sudo add-apt-repository -y ppa:ondrej/php
sudo update-alternatives --config phpsudo vi /etc/php/8.4/fpm/pool.d/www.conf:
pm.max_children = 10 (or divide available RAM minus
buffer by average memory used per request - check using
memory_get_peak_usage() or Laravel Debugbarpm.start_servers = 4pm.min_spare_servers = 2pm.max_spare_servers = 5pm.max_requests = 500/etc/php/8.4/fpm/pool.d/<site>.conf
[laravel]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm-laravel.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 15
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 7
pm.max_requests = 500
php_admin_value[memory_limit] = 256M
php_admin_value[error_log] = /var/log/php8.4-fpm-laravel2.log
php_admin_flag[log_errors] = on
[wordpress]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm-wordpress.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
php_admin_value[memory_limit] = 256M
php_admin_value[error_log] = /var/log/php8.4-fpm-wordpress.log
php_admin_flag[log_errors] = on
sudo php-fpm8.4 -ttsudo systemctl restart php8.4-fpmcomposer:
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"php composer-setup.phpphp -r "unlink('composer-setup.php');"sudo mv composer.phar /usr/local/bin/composersudo vi /etc/php/<VERSION>/fpm/php.ini
date.timezone = Asia/Shanghaiupload_max_filesize = 200Mpost_max_size = 200Mmemory_limit = 256Mdisable_functions = exec,passthru,shell_exec,systemexpose_php = Offsudo systemctl restart php8.4-fpmsudo apt install -y fail2ban ufw
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
sudo systemctl status fail2ban
sudo tee /etc/fail2ban/jail.d/custom.conf > /dev/null <<'EOF'
[DEFAULT]
bantime = 1d
findtime = 1d
ignoreip = 127.0.0.1/8 192.168.0.0/16
maxretry = 1
banaction = ufw
banaction_allports = ufw
EOF
sudo tee /etc/fail2ban/filter.d/ufw.conf > /dev/null <<'EOF'
[Definition]
failregex = [UFW BLOCK].+SRC=<HOST> DST
ignoreregex =
EOF
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw allow out http
sudo ufw allow out https
sudo ufw allow out 465 sudo ufw allow out 587
sudo ufw allow out 22 sudo ufw allow out 53
sudo ufw enable sudo ufw status verbose
sudo ls -alt /etc/fail2ban/filter.d/nginx*
sudo tee /etc/fail2ban/filter.d/nginx-sslerror.conf > /dev/null <<'EOF'
# Fail2Ban filter for Nginx SSL handshake failures
[Definition]
failregex = \[crit\] \d+#\d+: \*\d+ SSL_do_handshake\(\) failed \(SSL: error:1417D18C:SSL routines:tls_process_client_hello:version too low\) while SSL handshaking, client: <HOST>, server: \S*\s*$
ignoreregex =
EOF
sudo tee /etc/fail2ban/filter.d/nginx-4xx.conf > /dev/null <<'EOF'
# Fail2Ban filter for Nginx 4xx errors
[Definition]
failregex = ^<HOST>.*"(GET|POST).*" (404|444|403|400) .*
ignoreregex = .*(robots.txt|favicon.ico|jpg|png)
EOF
sudo tee /etc/fail2ban/filter.d/nginx-forbidden.conf > /dev/null <<'EOF'
# Fail2Ban filter for directory index forbidden errors
[Definition]
failregex = directory index of .+ is forbidden, client: <HOST>, server: .+
ignoreregex =
EOF
sudo tee /etc/fail2ban/filter.d/nginx-wp-login.conf > /dev/null <<'EOF'
# Fail2Ban filter for WordPress wp-login.php attempts
[Definition]
failregex = ^<HOST>.*"(POST|GET) /wp-login.php
ignoreregex =
EOF
sudo tee /etc/fail2ban/filter.d/nginx-botsearch.conf > /dev/null <<'EOF'
# Fail2Ban filter to match web requests for selected URLs that don't exist
[INCLUDES]
before = botsearch-common.conf
[Definition]
# Match 404 errors for selected URLs
failregex = ^<HOST> - \S+ \[\] "(GET|POST|HEAD) /<block> \S+" 404 .*$
# Match Nginx error log "No such file or directory" messages
failregex += ^\[error\] \d+#\d+: \*\d+ (\S+ )?"\S+" (failed|is not found) \(2: No such file or directory\), client: <HOST>, server: \S*, request: "(GET|POST|HEAD) /<block> \S+",.*
ignoreregex =
# Optional: can define datepattern if needed, but default works
# datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S
EOF
sudo tee /etc/fail2ban/filter.d/nginx-http-auth.conf > /dev/null <<'EOF'
# Fail2Ban filter for Nginx HTTP auth failures
[Definition]
failregex = ^\[error\] \d+#\d+: \*\d+ user "(?:[^"]+|.*?)":? (password mismatch|was not found in "[^"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d\.\d", host: "\S+"(?:, referrer: "\S+")?\s*$
ignoreregex =
EOF
sudo tee /etc/fail2ban/filter.d/nginx-limit-req.conf > /dev/null <<'EOF'
# Fail2Ban filter for Nginx limit_req module
[Definition]
# Zones defined in nginx configuration (comma-separated)
ngx_limit_req_zones = [^"]+
# Match Nginx limit_req exceeded messages
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex =
EOF
sudo tee /etc/fail2ban/filter.d/nginx-wp-xmlrpc.conf > /dev/null <<'EOF'
[Definition]
failregex = ^<HOST>.*"(POST|GET) /xmlrpc.php
ignoreregex =
EOF
sudo tee -a /etc/fail2ban/jail.d/custom.conf > /dev/null <<'EOF'
[sshd]
enabled = true
[nginx-4xx]
enabled = true
port = http,https
filter = nginx-4xx
logpath = %(nginx_access_log)s
maxretry = 15
findtime = 10m
bantime = 1h
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = %(nginx_error_log)s
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = %(nginx_access_log)s
[nginx-forbidden]
enabled = true
port = http,https
filter = nginx-forbidden
logpath = %(nginx_error_log)s
[nginx-sslerror]
enabled = true
port = http,https
filter = nginx-sslerror
logpath = %(nginx_error_log)s
[nginx-wp-login]
enabled = true
filter = nginx-wp-login
port = http,https
logpath = %(nginx_access_log)s
maxretry = 5
findtime = 10m
bantime = 12h
[nginx-wp-xmlrpc]
enabled = true
filter = nginx-wp-xmlrpc
port = http,https
logpath = %(nginx_access_log)s
maxretry = 3
findtime = 10m
bantime = 24h
[ufw]
enabled = true
filter = ufw
logpath = /var/log/ufw.log
EOF
sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'
[DEFAULT]
banaction = iptables-multiport
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
EOF
sudo systemctl restart fail2ban
sudo fail2ban-client status
fail2ban-client set ufw unbanip 192.0.2.2
sudo apt-get install -y supervisor
sudo apt install -y clamavsudo freshclamclamscan -r /file-to-scansudo apt install -y aide msmtp msmtp-mta bsd-mailx -
Do not install AppArmorvi ~/.msmtprc - Use an app-specific
password from Zoho, not your regular password.# Default settings
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
# Zoho SMTP account
account zoho
host smtp.zoho.com
port 587
from your_email@yourdomain.com
user your_email@yourdomain.com
password YOUR_ZOHO_APP_PASSWORD
# Make this account default
account default : zoho
chmod 600 ~/.msmtprcchown $(whoami):$(whoami) ~/.msmtprcvi ~/.mailrcset sendmail="/usr/bin/msmtp"
set use_from=yes
set from="ifor@designedbywaldo.com"
echo "This is a test" | mail -s "Testing Zoho" ifor@cors.techsudo apt install -y aide
sudo aideinitsudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.dbsudo crontab -eMAILTO="your_email@yourdomain.com"
0 3 * * * /usr/bin/aide --check | mail -s "AIDE Alert on $(hostname)" your_email@yourdomain.com
0 3 * * * /usr/bin/aide --check | gzip | mail -s "AIDE Alert on $(hostname)" -a "Content-Type: application/gzip" your_email@yourdomain.com # Optionally compress alerts
sudo aide --check | mail -s "AIDE Test on $(hostname)" your_email@yourdomain.comsudo apt install -y auditd audispd-plugins
sudo systemctl enable auditd
sudo mysql -u root -pCREATE USER 'NEW_USERNAME'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'NEW_STRONG_PASSWORD';
FLUSH PRIVILEGES;
# For each database required:
CREATE DATABASE NEW_DATABASE_NAME CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT CREATE TEMPORARY TABLES, LOCK TABLES ON *.* TO 'NEW_USERNAME'@'localhost';
FLUSH PRIVILEGES;
GRANT REFERENCES, SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON NEW_DATABASE_NAME.* TO 'NEW_USERNAME'@'localhost';
FLUSH PRIVILEGES;
EXIT;
git clone git@github.com:iforwms/karst-climber.git /var/www/karst-climber-laraveldeploy-file .env <server> /var/www/<project-name>./deploy-localsudo -u www-data crontab -e
* * * * * cd /var/www/karst-climber-laravel && php artisan schedule:run >> /dev/null 2>&1fix-laravel-permissionsphp artisan storage:linkphp artisan config:clearphp artisan route:clearphp artisan view:clearsudo vi /etc/logrotate.d/laravel-worker and add the
following:/var/www/cindra-laravel/worker.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
su www-data www-data
}
sudo logrotate -d /etc/logrotate.d/laravel-workersudo logrotate -f /etc/logrotate.d/laravel-worker[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/karst-climber-laravel/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/karst-climber-laravel/worker.log
stopwaitsecs=3600
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"
sudo supervisorctl restart alldefine('DISABLE_WP_CRON', true);define('DISALLOW_FILE_EDIT', true);define('WP_AUTO_UPDATE_CORE', true);define('FS_METHOD', 'direct');define('WP_MEMORY_LIMIT', '256M');sudo crontab -e
5 * * * * /usr/bin/php /var/www/karst-climber-wp/wp-cron.phpcurl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.pharphp wp-cli.phar --infochmod +x wp-cli.pharsudo mv wp-cli.phar /usr/local/bin/wpsudo mv wp-cli.phar /usr/local/bin/wpwp --info# Navigate to your WordPress root
cd /srv/www/wordpress
# Ensure the key writable directories exist
mkdir -p wp-content/upgrade-temp-backup/plugins
mkdir -p wp-content/wflogs
mkdir -p wp-content/uploads
# Set ownership to the web server user
sudo chown -R www-data:www-data wp-content/upgrade-temp-backup wp-content/wflogs wp-content/uploads
# Set directories to 755
sudo find wp-content/upgrade-temp-backup wp-content/wflogs wp-content/uploads -type d -exec chmod 755 {} \;
# Set files to 644
sudo find wp-content/upgrade-temp-backup wp-content/wflogs wp-content/uploads -type f -exec chmod 644 {} \;server {
# listen 443 ssl;
listen 80;
server_name SITE.com;
root /var/www/SITE/public;
# # Lovable Only - not quite working
# add_header Content-Security-Policy "
# default-src 'self' https:;
# script-src 'self' https: 'unsafe-inline' 'unsafe-eval';
# style-src 'self' https: 'unsafe-inline';
# img-src 'self' data: https:;
# font-src 'self' https: data:;
# connect-src 'self' https: wss://SUPABASE_ENDPOINT.supabase.co;
# frame-src 'self' https:;
# object-src 'none';
# base-uri 'self';
# form-action 'self';
# " always;
# Laravel Only
add_header Content-Security-Policy "
default-src 'self' https:;
script-src 'self' https: 'unsafe-inline' 'unsafe-eval';
style-src 'self' https: 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https: data:;
connect-src 'self' https:;
frame-src 'self' https:;
object-src 'none';
base-uri 'self';
form-action 'self';
" always;
# Wordpress Only
add_header Content-Security-Policy "
default-src 'self' https:;
script-src 'self' https: 'unsafe-inline' 'unsafe-eval';
style-src 'self' https: 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https: data:;
connect-src 'self' https:;
frame-src 'self' https:;
media-src 'self' https:;
object-src 'none';
base-uri 'self';
form-action 'self' https:;
" always;
index index.php;
charset utf-8;
# -----------------------------
# Allow Let's Encrypt ACME challenge
# -----------------------------
location /.well-known/acme-challenge/ {
root /var/www/karst-climber-laravel/public;
allow all;
}
location / {
# try_files $uri $uri/ /index.html; # ReactJS
# try_files $uri $uri/ /index.php?$query_string; # PHP
}
# -----------------------------
# Deny PHP execution in storage, uploads, cache, tmp, files (Laravel & WordPress)
# -----------------------------
location ~* ^/(storage|uploads|wp-content/(uploads|cache|tmp|files|storage))/.*\.(php|phtml|phar)$ {
deny all;
}
# -----------------------------
# Deny access to sensitive files
# -----------------------------
location ~* ^/(?:\.env|composer\.(json|lock)|artisan|server\.php|phpunit\.xml|wp-config\.php|\.htaccess|readme\.html|license\.txt|xmlrpc\.php)$ {
deny all;
}
# -----------------------------
# Deny access to hidden files (dotfiles), except .well-known
# -----------------------------
location ~ /\.(?!well-known).* {
deny all;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
error_log /var/log/nginx/SITE-error.log error;
sendfile off;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
# fastcgi_split_path_info ^(.+?\.php)(/.+)?$;
# fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_hide_header X-Powered-By;
}
# # Path to the SSL certificate and key files generated by acme.sh
# ssl_certificate /var/www/acme/SITE.com_ecc/fullchain.cer;
# ssl_certificate_key /var/www/acme/SITE.com_ecc/SITE.com.key;
# Enable SSL protocols and specify the allowed ciphers
# # OLD - ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-HA256:ECDHE-RSA-AES128-GCM-SHA256';
# ssl_prefer_server_ciphers on;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
}
#server {
# listen 443 ssl;
# server_name www.SITE.com;
# ssl_certificate /var/www/acme/SITE.com_ecc/fullchain.cer;
# ssl_certificate_key /var/www/acme/SITE.com_ecc/SITE.com.key;
# # Enable SSL protocols and specify the allowed ciphers
# # OLD - ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SA256:ECDHE-RSA-AES128-GCM-SHA256';
# ssl_prefer_server_ciphers on;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
# return 301 https://SITE.com$request_uri;
#}
#server {
# listen 80;
# server_name SITE.com www.SITE.com;
# return 301 https://SITE.com$request_uri;
#}
curl https://get.acme.sh | sh -s email=ifor@cors.techsudo mkdir -p /var/www/acmesudo cp -r /home/ifor/.acme.sh /var/www/acmesudo chown -R www-data:www-data /var/www/acmesudo chmod +x /var/www/acme/.acme.sh/acme.shsudo crontab -u www-data -e:0 3 * * * /var/www/acme/.acme.sh/acme.sh --cron --home /var/www/acme --quiet--reloadcmd "systemctl reload nginx" to allow
auto-renewing /var/www/acme/.acme.sh/acme.sh --home /home/ifor/.acme.sh --install-cert -d bikeasia.com -d www.bikeasia.com \
--key-file /home/ifor/.acme.sh/bikeasia.com_ecc/bikeasia.com.key \
--fullchain-file /home/ifor/.acme.sh/bikeasia.com_ecc/fullchain.cer \
--reloadcmd "systemctl reload nginx"
sudo mkdir -p /var/www/SITE_NAME/public/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/SITE_NAME/public/.well-known
sudo chmod -R 755 /var/www/SITE_NAME/public/.well-knownWordpress/Standard:
sudo -u www-data -H /var/www/acme/.acme.sh/acme.sh \
--issue \
-d SITE_NAME.com -d www.SITE_NAME.com \
-w /var/www/SITE_NAME \
--server letsencrypt \
--home /var/www/acme \
--force
Laravel:
sudo -u www-data -H /var/www/acme/.acme.sh/acme.sh \
--issue \
-d SITE_NAME.com -d www.SITE_NAME.com \
-w /var/www/SITE_NAME/public \
--server letsencrypt \
--home /var/www/acme \
--forcesudo apt-get update
sudo apt-get install -y fonts-wqy-microhei fonts-wqy-microhei fonts-wqy-zenhei fonts-wqy-zenhei fonts-noto-cjk
sudo chown -R www-data:www-data /var/www/karst-climber-wpsudo find /var/www/karst-climber-wp -type d -exec chmod 755 {} \;sudo find /var/www/karst-climber-wp -type f -exec chmod 644 {} \;sudo chmod 600 /var/www/karst-climber-wp/wp-config.phpecho "<?php // silence ?>" | sudo tee /var/www/karst-climber-wp/wp-content/uploads/index.phpsudo find /var/www/karst-climber-wp/wp-content/uploads -type f -name "*.php" -deletesudo vi /wp-content/themes/<active-theme>/functions.php
- remove_action('wp_head', 'wp_generator');sudo -u www-data crontab -e and remove any malicious
codesudo ufw default deny outgoingsudo ufw allow out to any port 53sudo ufw allow out to any port 443sudo ufw allow out to 127.0.0.1 port 3306cd /var/www/PROJECT-DIRsudo chown -R root:root .sudo chown -R www-data:www-data storage bootstrap/cachesudo find . -type d -exec chmod 755 {} \;sudo find . -type f -exec chmod 644 {} \;location ~* /(storage|uploads|files|tmp|cache)/.*\.php$ {
deny all;
}
location ~ /\.(?!well-known).* {
deny all;
}
location ~* (base64_encode|eval\(|assert\(|shell_exec|passthru|system\() {
deny all;
}
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
sudo tar \
--no-same-owner \
--no-same-permissions \
--exclude='storage/app/backup-temp' \
--exclude='storage/framework' \
--exclude='storage/logs' \
--exclude='storage/debugbar' \
--exclude='storage/app/temp' \
--exclude='storage/media-library/temp' \
-czf /tmp/laravel_storage_clean.tgz \
storage/app \
storage/media-library
clamscan -r /tmp/husfokus_laravel_storage_clean.tgz *If
low on memory, clamav will fail. To temporarily increase swap file
size:sudo fallocate -l 2G /swapfilesudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfileswapon --showfree -hsudo freshclamsudo swapoff /swapfilesudo rm -f /swapfilesudo clamscan -r -i --max-filesize=25M --log=/tmp/clamav_scan.log \
/var/www/karst-climber/storage \
/var/www/karst-climber-wp/wp-content/uploads
mysqldump \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--default-character-set=utf8mb4 \
-u DB_USERNAME -p DATABASE_NAME > /tmp/SQL_FILENAME.sql
gzip /tmp/SQL_FILENAME.sql
ALTER USER 'DB_USER'@'localhost' IDENTIFIED BY 'NEW_SECURE_PASSWORD';
zcat /tmp/SQL_FILENAME.sql.gz | egrep -i 'base64|eval\(|gzinflate|shell_exec|system\(|passthru|exec\('rsync -avz --progress karst:/tmp/laravel_storage_clean.tgz .rsync -avz --progress laravel_storage_clean.tgz karst:/tmp/rsync -avz --progress karst:/tmp/SQL_FILENAME.sql.gz .rsync -avz --progress SQL_FILENAME.sql.gz karst:/tmp/cd /var/www/karst-climber-laravelsudo mkdir -p /var/www/karst-climber-laravel/storagetar -tzf /tmp/laravel_storage_clean.tgz | head -n 20sudo tar --no-same-owner --no-same-permissions -xzf /tmp/laravel_storage_clean.tgz -C /var/www/karst-climber-laravel/sudo chown -R www-data:www-data storagesudo find storage -type d -exec chmod 750 {} \;sudo find storage -type f -exec chmod 640 {} \;sudo chmod -R ug+rw storage bootstrap/cachefind storage -type f -perm /111find /var/www/karst-climber-laravel/storage -type f \
\( -iname "*.php" -o -iname "*.phtml" -o -iname "*.phar" -o -iname "*.sh" \)gunzip /tmp/cindra_laravel.sqlmysql -u cindra_laravel_user -p cindra_laravel < /tmp/cindra_laravel.sqlsudo mysqldump --all-databases > ~/downloads/all-databases.sql
Old server to new server:
sudo rsync -avz --progress /path/to/dir/ root@NEW_IP:/path/to/dir/
New server from old server:
rsync -avz --progress ifor@OLD_IP:/path/to/dir/ /path/to/dir/
for f in /var/www/ /etc/letsencrypt/ /var/lib/letsencrypt/ /etc/nginx/h5bp/ /etc/apache2/.htpasswd /home/ifor/.acme.sh/ /etc/nginx/conf.d/ /home/ifor/downloads/ /home/ifor/backups/; do sudo rsync -avz --progress "$f" root@NEW_IP:"$f"; donesudo mysql < ~/downloads/all-databases.sqlGRANT ALL PRIVILEGES ON *.* TO 'server'@'localhost' WITH GRANT OPTION;)sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show
free -hsvi /etc/fstab:
/swapfile none swap sw 0 0svi /etc/sysctl.conf:
vm.swappiness=10
svi /etc/php/8.3/fpm/pool.d/www.conf
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 200svi /etc/php/8.3/fpm/php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60svi /etc/mysql/mysql.conf.d/mysqld.cnf
innodb_buffer_pool_size = 128M
innodb_log_buffer_size = 16M
max_connections = 20
key_buffer_size = 32M
tmp_table_size = 32M
max_heap_table_size = 32M
table_open_cache = 400Disable unused services:
systemctl list-unit-files --type=service --state=enabled