Published: Jun 23, 2026
To setup a secure web server, follow these steps:
0. setup environment
this script is only for root only, need to run with sudo or user root directly, and the script will exit at any error
# Ensure the script is run as root
if [ "$EUID" -ne 0 ]; then
echo "Error: Please run this script as root or with sudo."
exit 1
fi
# Exit immediately if a command exits with a non-zero status
set -e
1. update first
do the update first!
apt update -y && apt upgrade -y
2. Install Essentials & SSH Server
install needed packages!
apt install -y curl wget unzip git openssh-server ufw
3. Install Nginx
apt install -y nginx
systemctl enable nginx
systemctl start nginx
4. Install PHP and Extensions (System Default via Alternatives)
apt install -y php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl php-bcmath
5. Install & Secure MySQL Server
echo "--> Securing MySQL installation..."
mysql -e "
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
"
6. Install phpMyAdmin Manually (Cleanest setup for Nginx)
mkdir -p /var/www/html/phpmyadmin
cd /tmp
wget https://files.phpmyadmin.net/phpMyAdmin/${PMA_VERSION}/phpMyAdmin-${PMA_VERSION}-all-languages.tar.gz
tar -xzf phpMyAdmin-${PMA_VERSION}-all-languages.tar.gz
mv phpMyAdmin-${PMA_VERSION}-all-languages/* /var/www/html/phpmyadmin/
chown -R www-data:www-data /var/www/html/phpmyadmin
chmod -R 755 /var/www/html/phpmyadmin
# Create basic phpMyAdmin config file from sample
cp /var/www/html/phpmyadmin/config.sample.inc.php /var/www/html/phpmyadmin/config.inc.php
BLOWFISH_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
sed -i "s/\$cfg\['blowfish_secret'\] = '';/\$cfg\['blowfish_secret'\] = '${BLOWFISH_SECRET}';/g" /var/www/html/phpmyadmin/config.inc.php
7. Configure Nginx Server Block with Generic PHP Socket
cat << 'EOF' > /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.php index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ =404;
}
# Pass PHP scripts to FastCGI server using the generic, upgrade-safe socket
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
}
# Deny access to .htaccess files
location ~ /\.ht {
deny all;
}
}
EOF
touch /var/www/html/index.php
nginx -t
systemctl reload nginx
8. Setup SFTP-Only Group & SSH Jailing
by setup the SFTP-Only group, those users will be restricted to SFTP access only.
Note: ssh vs sshd service naming difference in Ubuntu 24.04 vs 26.04, so we will check which one is available and restart accordingly.
if ! getent group "$SFTP_GROUP" > /dev/null 2>&1; then
groupadd "$SFTP_GROUP"
fi
# Append Chroot Jail configuration to sshd_config safely if not already present
if ! grep -q "Match Group $SFTP_GROUP" /etc/ssh/sshd_config; then
cat << EOF >> /etc/ssh/sshd_config
# Custom SFTP-Only Chroot Jail
Match Group $SFTP_GROUP
ChrootDirectory %h
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
EOF
fi
# Restart SSH safely, accounting for OS naming differences (ssh vs sshd)
# (in Ubuntu 24.04, it is ssh.service, only in 26.04, added alias sshd.service)
if systemctl list-unit-files | grep -q "^ssh\.service"; then
systemctl restart ssh
else
systemctl restart sshd
fi
9. Setup basic firewall rules
ufw allow OpenSSH ufw allow 'Nginx Full' echo "y" | ufw enable
10. Install & Configure Fail2Ban
apt install -y fail2ban
# Create a custom phpMyAdmin filter to catch failed web logins
cat << 'EOF' > /etc/fail2ban/filter.d/phpmyadmin-auth.conf
[Definition]
failregex = ^ -.*"POST /phpmyadmin/index.php.* HTTP/.*" 200 .*
ignoreregex =
EOF
# Create the main jail configuration file
cat << 'EOF' > /etc/fail2ban/jail.local
[DEFAULT]
# Ban hosts for 1 hour (3600 seconds) after 5 failed attempts within 10 minutes, and Progressively x2
bantime = 3600
findtime = 600
maxretry = 5
banaction = ufw
bantime.increment = true
bantime.factor = 2
# Protect SSH & SFTP (They both share the sshd daemon)
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
# Protect phpMyAdmin via Nginx Access Logs
[phpmyadmin-auth]
enabled = true
port = http,https
filter = phpmyadmin-auth
logpath = /var/log/nginx/access.log /home/*/log/access.log
maxretry = 3
bantime = 7200
EOF
# Restart Fail2Ban to apply the new rules
systemctl enable fail2ban
systemctl restart fail2ban
11. Setup Certbot for SSL (Ready for production)
Certbot is installed via Snap, rather than through the default package manager, as it provides the most up-to-date version and seamless integration with Nginx.
apt install snapd -y
snap install core && snap refresh core
snap install --classic certbot
# Link the certbot command so it's globally accessible
if [ ! -f /usr/bin/certbot ]; then
ln -s /snap/bin/certbot /usr/bin/certbot
fi
12. Auto-updating system packages via unattended-upgrades
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections apt install -y unattended-upgrades
echo "=================================================================" echo " SETUP COMPLETE! " echo "================================================================="
please continue and finish it.