
August 19, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Securing a production web server requires moving beyond default installations to implement layered defenses that stop automated attacks before they reach your application. Implementing comprehensive Ubuntu Server Security Best Practices is the foundation for running reliable Laravel, WordPress, or custom PHP applications in 2026, whether hosted locally in Kathmandu or on global cloud infrastructure. This guide covers the exact hardening steps I use on client servers to prevent unauthorized access, mitigate brute-force attempts, and maintain compliance without sacrificing deployment velocity.
What Are the Essential Ubuntu Server Security Best Practices for Production?
A secure Ubuntu server relies on defense-in-depth rather than a single silver bullet. In my experience maintaining production environments for legal-tech portals and eCommerce platforms, the most effective approach combines network-level filtering, access control hardening, automated maintenance, and application isolation. When evaluating how to secure your website and server in Nepal or abroad, these core principles remain constant regardless of the specific framework or CMS you deploy.
The priority order matters. Network filtering must come first because it reduces the attack surface before any service processes a request. Access control comes second because compromised credentials bypass most application-level protections. Automated patching ensures known vulnerabilities don't persist for weeks while you focus on feature development. Finally, proper file permissions contain damage if an attacker does achieve code execution through your application.
How Do You Harden SSH Access on Ubuntu 24.04?
SSH is the primary attack vector for automated bots scanning the internet. Default configurations on Ubuntu 24.04 LTS are functional but not hardened for hostile environments. On every production server I manage, from legal document portals to high-traffic WooCommerce stores, I apply these modifications immediately after provisioning.
Disable Root Login and Password Authentication
Root login via SSH provides attackers with a known username and maximum privileges upon successful compromise. Password authentication enables brute-force attacks regardless of password complexity. Edit /etc/ssh/sshd_config with these directives:
# /etc/ssh/sshd_config - Production hardened configuration
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy admin
Protocol 2 After editing, validate the configuration syntax before restarting to avoid locking yourself out:
sudo sshd -t
sudo systemctl restart sshd Configure Key-Based Authentication Correctly
Generate ED25519 keys on your local machine, not the server. ED25519 offers better security and performance than RSA for modern OpenSSH versions:
ssh-keygen -t ed25519 -C "deploy@production-server-2026"
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip Store private keys securely with appropriate permissions (chmod 600). For teams managing multiple servers, consider using SSH certificates or HashiCorp Vault for centralized key management rather than distributing individual public keys. This becomes especially important when coordinating deployments across sister sites sharing infrastructure, as I do with several Nepal-based legal service platforms.
Change Default SSH Port (Optional Defense-in-Depth)
Moving SSH from port 22 to a non-standard port like 2222 reduces noise from automated scanners but doesn't provide real security against targeted attacks. If you implement this, update your UFW rules accordingly and document the port change in your team's runbook. Never rely on obscurity as your only protection.
How Should You Configure UFW Firewall Rules for Web Servers?
UFW (Uncomplicated Firewall) provides a straightforward interface to netfilter/iptables. The principle is simple: deny everything by default, then explicitly allow only what your application requires. For a typical Laravel or WordPress production server running Nginx or Apache with PHP-FPM, you need exactly three ports open.
# Reset to clean state (caution: will disconnect if not careful)
sudo ufw --force reset
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow essential services
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Optional: Rate limit SSH to slow brute force
sudo ufw limit 22/tcp comment 'SSH rate limit'
# Enable and verify
sudo ufw enable
sudo ufw status verbose If your application uses Redis on localhost only, do not open port 6379 externally. Same for MySQL/MariaDB on port 3306 — database connections should originate from the application server itself or through an SSH tunnel for remote administration. Opening database ports to the internet violates fundamental Ubuntu Server Security Best Practices and exposes you to data exfiltration risks.
| Port | Service | Action | Rationale |
|---|---|---|---|
| 22/tcp | SSH | Allow + Rate Limit | Administrative access; rate limiting slows automated attacks |
| 80/tcp | HTTP | Allow | Required for Let's Encrypt validation and HTTP→HTTPS redirects |
| 443/tcp | HTTPS | Allow | Encrypted web traffic; mandatory for production |
| 3306/tcp | MySQL | Block External | Bind to 127.0.0.1; use SSH tunnels for remote access |
| 6379/tcp | Redis | Block External | Localhost-only unless running dedicated cache cluster |
| 8080/tcp | Node.js Dev | Block in Prod | Development-only; reverse proxy through Nginx in production |
How Do You Set Up Fail2Ban to Block Brute-Force Attacks?
Even with key-only SSH authentication, monitoring and blocking malicious IPs provides valuable visibility into attack patterns. Fail2Ban scans log files and updates firewall rules dynamically when it detects suspicious behavior. On production servers hosting client-facing applications, I configure it for both SSH and web application endpoints.
# Install fail2ban
sudo apt update
sudo apt install fail2ban -y
# Create local configuration (never edit jail.conf directly)
sudo nano /etc/fail2ban/jail.local Add this configuration to /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
banaction = ufw
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
[laravel-auth]
enabled = true
port = http,https
filter = laravel-auth
logpath = /var/www/html/storage/logs/laravel.log
maxretry = 10
findtime = 300
bantime = 7200 For Laravel applications, create a custom filter at /etc/fail2ban/filter.d/laravel-auth.conf that matches failed login attempts in your application logs. This protects your authentication system at the network level before requests even reach PHP-FPM, reducing server load during credential stuffing attacks.
# Enable and start fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Verify active jails
sudo fail2ban-client status
sudo fail2ban-client status sshd Why Is Automated Security Patching Critical for Ubuntu Servers?
Manual patching fails in practice. Between client deadlines, feature development, and operational tasks, security updates slip through the cracks. Unattended Upgrades automates installation of security patches without intervention, ensuring known vulnerabilities get patched within 24 hours of release. This single practice prevents the majority of successful server compromises I've investigated over fifteen years.
# Install unattended-upgrades
sudo apt install unattended-upgrades apt-listchanges -y
# Enable automatic updates
sudo dpkg-reconfigure --priority=low unattended-upgrades
# Configure /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
};
Unattended-Upgrade::Package-Blacklist {
"php8.3-fpm";
"nginx";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Mail "admin@yourdomain.com"; Blacklisting critical packages like php8.3-fpm and nginx prevents automatic restarts during business hours. Schedule reboots for off-peak times (4 AM Nepal Time works well for domestic traffic patterns). Test this configuration thoroughly in staging before deploying to production — automatic reboots during peak traffic can cause revenue loss for eCommerce clients.
What File Permissions Protect Laravel and WordPress Applications?
Incorrect file permissions are among the most common vulnerabilities I encounter when auditing existing deployments. Overly permissive settings allow attackers who gain limited access to modify application code, inject malware, or escalate privileges. Proper ownership and mode bits contain blast radius.
- Web root ownership: Files owned by
www-data:www-dataor dedicated deploy user, never root - Directory permissions: 755 for directories (owner write, group/world read+execute)
- File permissions: 644 for files (owner write, group/world read)
- Storage directories: Laravel's
storage/andbootstrap/cache/require 775 for group write access - Configuration files:
.envset to 600 (owner read/write only), never world-readable - Upload directories: Restrict executable permissions; validate uploads at application level
# Set correct ownership for Laravel application
sudo chown -R deploy:www-data /var/www/html/myapp
sudo find /var/www/html/myapp -type d -exec chmod 755 {} \;
sudo find /var/www/html/myapp -type f -exec chmod 644 {} \;
# Laravel-specific writable directories
sudo chgrp -R www-data /var/www/html/myapp/storage
sudo chgrp -R www-data /var/www/html/myapp/bootstrap/cache
sudo chmod -R 775 /var/www/html/myapp/storage
sudo chmod -R 775 /var/www/html/myapp/bootstrap/cache
# Secure environment file
sudo chmod 600 /var/www/html/myapp/.env
sudo chown deploy:deploy /var/www/html/myapp/.env When working with Laravel developers in Nepal or international teams, enforce these permissions through deployment scripts rather than manual correction. Deployer 7 handles this automatically with proper shared_dirs and writable_dirs configuration, preventing permission drift between releases. For detailed deployment workflows, see my guide on CI/CD pipeline setup for production servers.
Implementing Ubuntu Server Security Best Practices as Standard Operating Procedure
Security isn't a one-time configuration task — it's an ongoing operational discipline. The Ubuntu Server Security Best Practices outlined here form the baseline for every production server I provision, whether for a Kathmandu law firm's client portal or an international eCommerce platform. Document these procedures in your team's runbook, automate enforcement through configuration management tools like Ansible or Deployer, and audit compliance quarterly.
Start with SSH hardening and UFW today — they take thirty minutes and eliminate entire attack categories. Add fail2ban and Unattended Upgrades this week. Review file permissions during your next deployment. These incremental improvements compound into a security posture that withstands real-world threats without slowing development velocity. If you need hands-on assistance securing your production infrastructure or want a comprehensive audit of your current setup, reach out to discuss your server security requirements.

