Setup a secure and bulltet proof web server on ubuntu

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 "================================================================="

Comments (1)

smile Jun 25, 2026 @ 01:25 AM

please continue and finish it.


Leave a Comment

Your email address will not be published.
Back to Tech Notes