Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Ubuntu Security Hardening Guide

By Kokil Thapa | Last reviewed: August 2026

Shipping a web application without a structured Ubuntu Security Hardening Guide is the most common cause of preventable breaches I see in Nepal’s hosting market. Default Ubuntu 24.04 LTS installations are functional but expose unnecessary attack surfaces through open ports, root login, and permissive file permissions. This guide provides the exact hardening sequence I use for production Laravel and WordPress systems, transforming a fresh VPS into a defensible platform before deploying a single line of application code.

How Do You Configure SSH and UFW for Ubuntu Security Hardening?

SSH and firewall configuration form the perimeter of any secure website and server infrastructure in Nepal. On a fresh Ubuntu 24.04 LTS install, password authentication and root login are often enabled by default, and no firewall rules exist. These defaults must be corrected immediately after initial access.

Hardening SSH Access

Password-based SSH authentication is vulnerable to brute-force attacks regardless of password complexity. Key-based authentication eliminates this vector entirely. Generate an ED25519 key pair on your local machine (not the server) using ssh-keygen -t ed25519 -C "admin@yourdomain.com", then copy the public key to the server with ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip. Verify key-based login works in a new terminal session before modifying the SSH daemon configuration.

Edit /etc/ssh/sshd_config to enforce hardened settings:

<!-- /etc/ssh/sshd_config -->
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy admin
Protocol 2
X11Forwarding no
AllowTcpForwarding no

The AllowUsers directive restricts SSH access to named accounts only. Even if an attacker compromises another system account, they cannot authenticate via SSH unless explicitly listed. After editing, validate syntax with sshd -t and reload with systemctl reload sshd. Never restart sshd without testing first — a syntax error locks you out permanently.

Configuring UFW Firewall Rules

UFW (Uncomplicated Firewall) wraps nftables on Ubuntu 24.04. Enable it only after defining allow rules, or you will block your own SSH access:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable

For servers running non-standard SSH ports, replace port 22 with your chosen port. Avoid opening database ports (3306, 5432, 6379) to the public internet. Database connections should originate from localhost or a private VPC subnet. If remote database access is required for maintenance, use SSH tunneling instead of exposing the port directly.

Network Perimeter HardeningAdmin WorkstationED25519 Key Only✓ Password DisabledUFW FirewallAllow: 22, 80, 443Deny: 3306, 6379Application ServerPHP-FPM + NginxLocalhost DB OnlyBlocked External Traffic✗ Direct MySQL / Redis / SMTP AccessUse SSH Tunneling for Remote Maintenance
Ubuntu Security Hardening Guide network perimeter: SSH key authentication passes through UFW to reach the application server while database ports remain blocked externally.

What Kernel and Service Settings Are Essential in an Ubuntu Security Hardening Guide?

Network-level hardening extends beyond the firewall into kernel parameters. Default Linux TCP/IP stack settings prioritize compatibility over security, leaving servers vulnerable to SYN floods, IP spoofing, and man-in-the-middle attacks. Apply these sysctl settings in /etc/sysctl.d/99-hardening.conf:

# Prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Log suspicious packets
net.ipv4.conf.all.log_martians = 1

# Enable SYN cookies against SYN flood attacks
net.ipv4.tcp_syncookies = 1

# Disable IPv6 if unused
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

Apply changes with sysctl --system. The rp_filter setting enables reverse path filtering, which drops packets claiming to originate from addresses that would not route back through the receiving interface — a primary defense against IP spoofing. SYN cookies allow the server to handle connection requests without allocating memory until the three-way handshake completes, mitigating resource exhaustion during SYN flood attacks.

Disabling Unnecessary Services

Every running service is a potential attack surface. Audit active services with systemctl list-units --type=service --state=running. Common candidates for removal on a dedicated web server include:

  • cups — Print service, never needed on a headless server
  • avahi-daemon — mDNS/Bonjour discovery, exposes hostname on local networks
  • rpcbind — NFS/RPC service, frequent target for amplification attacks
  • bluetooth — No legitimate use case on production web infrastructure

Disable and mask each unnecessary service: systemctl disable --now cups && systemctl mask cups. Masking prevents accidental re-enablement by package upgrades or dependency resolution.

How Does fail2ban Strengthen Your Ubuntu Security Hardening Guide?

Even with key-only SSH and UFW enabled, attackers probe exposed services continuously. fail2ban monitors log files for malicious patterns and dynamically updates firewall rules to block offending IPs. Install it with apt install fail2ban and create a local jail configuration at /etc/fail2ban/jail.local:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log

[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 300

For WordPress sites, create a custom filter at /etc/fail2ban/filter.d/wordpress.conf targeting XML-RPC abuse and login page brute-forcing:

[Definition]
failregex = ^<HOST>.*POST.*(xmlrpc\.php|wp-login\.php).*(403|401|200)
ignoreregex =

Enable and start fail2ban with systemctl enable --now fail2ban. Monitor active bans with fail2ban-client status sshd. In my experience managing shared EC2 infrastructure for multiple legal-tech portals, fail2ban typically blocks hundreds of automated probes per week per server. Without it, even hardened SSH configurations generate excessive failed-authentication log noise that obscures genuine threats.

fail2ban Intrusion Prevention FlowService Logsauth.lognginx/error.logaccess.logPattern MatchRegex Filtersmaxretry: 3findtime: 600sDynamic BanUFW Rule Insertbantime: 3600sIP BlockedAutomatic Unban After bantime ExpiresPrevents Permanent Lockout of Legitimate Users Behind Shared IPs
fail2ban monitors service logs, matches attack patterns against configured filters, and inserts temporary UFW ban rules that expire automatically after bantime elapses.

Why Is PHP-FPM Pool Isolation Critical for Multi-Site Ubuntu Security Hardening?

On servers hosting multiple Laravel or WordPress sites, running all applications under a single PHP-FPM pool creates catastrophic cross-site contamination risk. If one site is compromised, the attacker inherits file-system access to every other site sharing that pool. Pool isolation assigns each site its own Unix socket, user account, and open_basedir restriction.

Create a dedicated system user for each site: useradd -r -s /usr/sbin/nologin -d /var/www/site1 site1. Then configure an isolated pool at /etc/php/8.4/fpm/pool.d/site1.conf:

[site1]
user = site1
group = site1
listen = /run/php/php8.4-fpm-site1.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 4

; Security isolation
open_basedir = /var/www/site1:/tmp
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
chdir = /var/www/site1/public
php_admin_value[upload_tmp_dir] = /var/www/site1/tmp
php_admin_value[session.save_path] = /var/www/site1/sessions

The open_basedir directive restricts PHP file operations to the site's directory tree and /tmp. Combined with disable_functions, this prevents compromised scripts from executing system commands or traversing outside their designated root. Set ownership correctly: chown -R site1:site1 /var/www/site1 and ensure Nginx references the correct socket in its upstream configuration.

Isolation LevelCross-Site RiskResource OverheadRecommended For
Single shared poolCritical — full filesystem accessMinimalNever in production
Per-site user + open_basedirLow — contained to site directoryModerateMulti-tenant Laravel/WordPress
Containerized (Docker/LXC)Negligible — kernel namespace isolationHigherHigh-security legal-tech / fintech

For legal-tech portals handling sensitive client documents, I recommend containerization despite higher operational complexity. For standard business sites and eCommerce platforms like those built for Nepal eCommerce projects, per-site PHP-FPM pools with open_basedir provide sufficient isolation without container orchestration overhead.

How Do Automated Updates and File Permissions Complete Ubuntu Security Hardening?

Manual patching fails because humans forget. Ubuntu's unattended-upgrades package automates security patch installation without requiring intervention. Install and configure it:

apt install unattended-upgrades apt-listchanges
dpkg-reconfigure unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboot during low-traffic windows and restrict upgrades to security repositories only:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";

File permissions are equally critical. Web-accessible directories should be owned by the site user with 755 permissions for directories and 644 for files. The storage/ and bootstrap/cache/ directories in Laravel require write access: chown -R site1:www-data /var/www/site1/storage && chmod -R 775 /var/www/site1/storage. Never set 777 permissions anywhere — this grants write access to every system user and negates pool isolation entirely.

Layered Defense ModelLayer 1: Network PerimeterUFW + SSH Key Auth + fail2banLayer 2: Kernel Hardeningsysctl rp_filter + SYN cookies + Service RemovalLayer 3: Application IsolationPHP-FPM Pools + open_basedir + File PermissionsLayer 4: Automated Maintenanceunattended-upgrades + Log Rotation + Backup Verification
Ubuntu Security Hardening Guide layered defense: each layer compensates for potential failures in the layer above, creating depth against sophisticated attacks.

Ubuntu Security Hardening Guide Implementation Checklist

Security hardening is not a one-time task but a repeatable process applied to every new server. Use this checklist as your deployment baseline:

  1. Create non-root admin user with sudo privileges and disable root SSH login
  2. Deploy ED25519 SSH keys and disable password authentication
  3. Configure UFW with explicit allow rules for SSH, HTTP, and HTTPS only
  4. Apply sysctl hardening parameters and verify with sysctl --system
  5. Install and configure fail2ban with jails for SSH, Nginx, and application-specific patterns
  6. Create isolated PHP-FPM pools with per-site users and open_basedir restrictions
  7. Set correct file ownership (site user) and permissions (755/644, never 777)
  8. Enable unattended-upgrades for automatic security patches with scheduled reboots
  9. Configure log rotation to prevent disk exhaustion from verbose security logging
  10. Document all changes in version-controlled server configuration repository

This Ubuntu Security Hardening Guide covers the foundation that protects your application layer. Hardening reduces attack surface but does not eliminate risk — combine it with regular audits, offsite backups, and application-level security practices. For teams managing multiple client sites across shared infrastructure, consider reading about CI/CD pipeline setup in Nepal to automate consistent hardening across deployments. If you need hands-on assistance securing your production environment or implementing these measures for a Laravel application in Nepal, reach out directly for a server security assessment tailored to your infrastructure.

Frequently Asked Questions

Update all packages, configure UFW to deny incoming traffic by default, disable root SSH login, enforce key-based authentication only, and set up automatic security updates via unattended-upgrades.

Freelance rates typically range from NPR 15,000 to 40,000 (USD 110–300) depending on complexity. I charge based on scope rather than hourly billing for hardening projects.

Immediately after initial OS installation and before deploying any application code or opening public network ports to prevent exposure during setup.

Always allow SSH port 22 or your custom port before enabling UFW. Run ufw allow 22/tcp then ufw enable. Test connectivity in a separate terminal session before closing your current connection. On production servers I manage, I also whitelist my office IP range as a backup access method. Never enable UFW without verifying SSH access works, as recovery requires console access through your hosting provider.

Root is a known username targeted by automated brute-force attacks. Disabling direct root login forces attackers to guess both a valid username and password. Create a sudo-enabled user account instead and set PermitRootLogin no in /etc/ssh/sshd_config. This single change eliminates the majority of successful SSH compromises I have seen on Nepali business servers running Laravel and WordPress applications.

Fail2ban monitors log files for repeated failed authentication attempts and dynamically updates UFW or iptables rules to ban offending IPs. Configure it for SSH, Apache/Nginx auth failures, and application-level endpoints. Set bantime to 3600 seconds minimum with findtime of 600 seconds. In my experience managing legal-tech portals, fail2ban blocks hundreds of daily probe attempts without legitimate user impact when tuned correctly.

Change the default port to reduce noise, set Protocol 2, disable password authentication with PasswordAuthentication no, limit MaxAuthTries to 3, and use AllowUsers or AllowGroups directives to restrict SSH access to specific accounts. Regenerate host keys if inheriting a server. These settings in /etc/ssh/sshd_config significantly reduce attack surface. Always restart sshd service after changes and verify access before disconnecting.

Install unattended-upgrades package and configure /etc/apt/apt.conf.d/50unattended-upgrades to automatically install security patches from Ubuntu security repositories. Enable automatic reboot for kernel updates during maintenance windows by setting Unattended-Upgrade::Automatic-Reboot true with a scheduled time. This prevents servers from running vulnerable kernels for weeks. I enable this on every production Ubuntu 22.04 and 24.04 server I deploy.

Set ownership to www-data for web directories with 755 for directories and 644 for files. Never use 777 permissions. Store sensitive configuration like .env files outside the web root or with 600 permissions owned by the application user. For Laravel deployments, ensure storage and bootstrap/cache directories are writable by www-data but not world-readable. Incorrect permissions are the most frequent vulnerability I fix on compromised Nepali eCommerce sites.

AppArmor enforces mandatory access control profiles that restrict what resources individual programs can access, even if compromised. Ubuntu ships with default profiles for MySQL, Nginx, and PHP-FPM. Enable and enforce these profiles rather than leaving them in complain mode. Custom profiles add defense-in-depth for application-specific binaries. While more complex to configure than basic file permissions, AppArmor limits damage from zero-day exploits and misconfigurations in production environments.

Add sysctl settings to /etc/sysctl.d/99-hardening.conf including net.ipv4.tcp_syncookies=1 for SYN flood protection, net.ipv4.conf.all.rp_filter=1 for reverse path filtering, and kernel.randomize_va_space=2 for ASLR. Disable IPv6 if unused with net.ipv6.conf.all.disable_ipv6=1. Apply changes with sysctl --system. These kernel-level protections complement application and network security layers. I include these in baseline hardening for all client infrastructure.

Use Lynis or OpenSCAP to scan against CIS Ubuntu benchmarks. Lynis runs locally with lynis audit system and provides prioritized remediation suggestions. OpenSCAP integrates with compliance frameworks for regulated environments. Schedule monthly automated scans and track score improvements over time. Manual review remains necessary as automated tools miss context-specific risks. I run Lynis quarterly on production servers hosting legal and financial client data.

Ensure rsyslog forwards critical logs to a remote syslog server or centralized logging platform. Configure auditd to track privileged commands, file access to sensitive paths, and authentication events. Retain logs for at least 90 days locally and longer offsite. Enable verbose SSH logging with LogLevel VERBOSE. Without centralized logging, attackers can delete local evidence after compromise. This is especially important for multi-server deployments where forensic analysis requires correlated timestamps.

SSL/TLS hardening focuses specifically on encrypted transport configuration while Ubuntu hardening covers the entire OS attack surface. Configure strong cipher suites, disable TLS 1.0/1.1, enable HSTS headers, and use modern certificate chains via Let's Encrypt. These complement but do not replace OS-level hardening. A server with perfect SSL but weak SSH configuration remains vulnerable. Both layers require attention in production deployments handling payments or personal data.

Yes, aggressive hardening can break applications relying on loose permissions, deprecated protocols, or unrestricted network access. Test thoroughly in staging before applying to production. Common issues include AppArmor blocking legitimate file operations, UFW rules preventing internal service communication, and disabled PHP functions required by legacy code. Incremental hardening with rollback plans prevents downtime. I always validate application functionality after each hardening change on client systems.

Share this article

Quick Contact Options
Choose how you want to connect me: