
August 25, 2026
12 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
A reliable Ubuntu Server Setup Guide is the difference between a production environment that sleeps through the night and one that wakes you up at 3 AM with 502 errors. Most tutorials stop at installing packages, leaving you with an insecure, unoptimized box that fails under real traffic. This guide covers the complete stack I use daily for client projects in Nepal and abroad: Ubuntu 24.04 LTS, Nginx, PHP 8.4-FPM, MySQL 8.4, and automated deployment pipelines. Whether you are hosting a high-traffic WooCommerce store or a custom Laravel legal-tech portal, these steps establish a secure, maintainable foundation that scales without unnecessary complexity.
How do you secure a fresh Ubuntu Server Setup Guide installation?
Security is not a feature you add later; it is the first step of any competent server hardening strategy. On a fresh Ubuntu 24.04 LTS install, your immediate priority is reducing the attack surface before installing a single web package. I have seen too many developers in Kathmandu expose port 3306 or leave password authentication enabled, only to face brute-force attacks within hours of provisioning.
Create a non-root sudo user
Never run web services as root. Create a dedicated administrative user immediately after first boot:
adduser deploy
usermod -aG sudo deploy
su - deploy All subsequent commands in this guide assume you are operating as this non-root user. If you are migrating from an older setup where everything runs as root, plan a maintenance window to refactor permissions. Running PHP-FPM or Nginx workers as root is a critical vulnerability that no amount of application-level security can mitigate.
Harden SSH access
Password authentication is obsolete for production servers. Generate an ED25519 key pair on your local machine and copy it to the server:
ssh-keygen -t ed25519 -C "deploy@production"
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip Edit /etc/ssh/sshd_config to enforce key-based authentication and disable root access:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2 Restart the SSH service with sudo systemctl restart sshd. Keep your current session open and test a new connection before closing the existing one. Getting locked out during initial setup is a rite of passage, but it is entirely avoidable with this verification step.
Configure UFW firewall rules
Ubuntu’s Uncomplicated Firewall (UFW) should be configured before exposing any services. The default policy must deny all incoming traffic except explicitly allowed ports:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose Note that we use the Nginx Full profile, which opens both port 80 and 443. Never open port 3306 (MySQL) or 6379 (Redis) to the public internet. Database connections should only come from localhost or a private VPC network. For teams working remotely across Nepal’s variable ISP infrastructure, consider adding rate limiting to SSH to prevent lockouts during legitimate access from dynamic IPs.
How do you configure Nginx and PHP 8.4-FPM for Laravel?
Nginx with PHP-FPM remains the gold standard for serving Laravel and Symfony applications in 2026. While Apache with mod_php still works, Nginx’s event-driven architecture handles concurrent connections more efficiently on resource-constrained VPS instances common in Nepal’s hosting market. PHP 8.4 is the current stable release and the minimum recommended version for Laravel 12.x, offering JIT improvements and property hooks that directly benefit framework performance.
Install the Ondřej Surý PPA
Ubuntu 24.04’s default repositories may not yet include PHP 8.4. Add the trusted PPA that has been the community standard for over a decade:
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.4-fpm php8.4-mysql php8.4-xml php8.4-curl \
php8.4-mbstring php8.4-zip php8.4-bcmath php8.4-gd php8.4-intl \
nginx mysql-server redis-server This installs PHP-FPM (not the CLI-only or Apache module variant), Nginx, MySQL 8.4, and Redis in a single operation. Pinning versions explicitly prevents accidental upgrades during routine apt upgrade cycles that could break production.
Tune PHP-FPM pool configuration
The default FPM pool is configured for minimal memory usage, not production throughput. Edit /etc/php/8.4/fpm/pool.d/www.conf to match your server’s RAM:
[www]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 60
php_admin_value[upload_max_filesize] = 64M
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_value[opcache.validate_timestamps] = 0 The pm.max_children value depends entirely on available RAM. Each PHP-FPM worker consumes approximately 30–50 MB under load. On a 4 GB VPS, 50 children is aggressive; start at 30 and monitor. Setting opcache.validate_timestamps = 0 is mandatory for production — it tells OPcache to never check for file changes, eliminating thousands of filesystem stat calls per request. You must reload PHP-FPM after every deployment to clear the cache.
Configure Nginx virtual host
Create /etc/nginx/sites-available/laravel-app with a configuration optimized for Laravel 12:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /var/www/laravel-app/current/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 32k;
fastcgi_busy_buffers_size 64k;
}
location ~ /\.(?!well-known).* {
deny all;
}
access_log /var/log/nginx/laravel-app.access.log;
error_log /var/log/nginx/laravel-app.error.log;
} The /var/www/laravel-app/current/public path assumes a symlinked release structure used by Deployer. This pattern enables zero-downtime deployments by atomically swapping the current symlink. Enable the site with sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/, test with sudo nginx -t, and reload with sudo systemctl reload nginx.
What database and caching stack supports production Laravel apps?
MySQL 8.4 LTS is the default choice for most Laravel applications I deploy. It offers window functions, CTEs, and improved JSON support that simplify complex queries in legal-tech portals and e-commerce reporting. PostgreSQL 17 is equally viable and sometimes preferable for GIS-heavy applications or when you need advanced indexing strategies, but MySQL’s ecosystem maturity in Nepal’s hosting landscape makes it the pragmatic default.
Secure MySQL 8.4 installation
Run the security script immediately after installation and make these specific choices:
sudo mysql_secure_installation
# VALIDATE PASSWORD COMPONENT: Yes (STRONG)
# REMOVE ANONYMOUS USERS: Yes
# DISALLOW ROOT LOGIN REMOTELY: Yes
# REMOVE TEST DATABASE: Yes
# RELOAD PRIVILEGE TABLES: Yes Create application-specific users with minimal privileges. Never connect as root from your Laravel application:
CREATE USER 'laravel_app'@'localhost' IDENTIFIED BY 'strong_random_password';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON laravel_db.* TO 'laravel_app'@'localhost';
FLUSH PRIVILEGES; Bind MySQL to localhost only by editing /etc/mysql/mysql.conf.d/mysqld.cnf and setting bind-address = 127.0.0.1. This single line prevents remote exploitation attempts entirely.
Configure Redis for queues and caching
Redis 7.x handles Laravel queues, session storage, and application caching far more reliably than file-based drivers. Edit /etc/redis/redis.conf:
bind 127.0.0.1 ::1
maxmemory 256mb
maxmemory-policy allkeys-lru
save ""
appendonly yes Disabling RDB snapshots (save "") and enabling AOF persistence provides better durability for queue jobs without the I/O overhead of periodic forks. Set maxmemory based on available RAM — 256 MB is sufficient for most mid-traffic Laravel apps. Restart Redis with sudo systemctl restart redis-server and verify connectivity with redis-cli ping.
| Component | Recommended Version (2026) | Purpose | Critical Config |
|---|---|---|---|
| PHP-FPM | 8.4.x | Application runtime | OPcache validate_timestamps=0 |
| Nginx | 1.26+ | Reverse proxy + static files | FastCGI buffer tuning |
| MySQL | 8.4 LTS | Primary data store | bind-address=127.0.0.1 |
| Redis | 7.4.x | Queues, cache, sessions | maxmemory-policy allkeys-lru |
| Certbot | Latest | TLS certificate automation | Auto-renewal cron enabled |
How do you automate deployments and SSL certificate management?
Manual deployments via FTP or SSH+git pull are unacceptable for production systems in 2026. I use Deployer 7 for every Laravel project because it provides atomic releases, shared persistent directories, and instant rollback — all without requiring Node.js or build tools on the production server. Frontend assets are compiled locally or in CI and committed as artifacts, keeping the production environment lean and secure.
Set up Deployer 7 for zero-downtime releases
Install Deployer globally via Composer and initialize your project:
composer global require deployer/deployer:^7.0
dep init Your deploy.php should define shared files and writable directories explicitly:
set('shared_files', ['.env']);
set('shared_dirs', ['storage', 'bootstrap/cache']);
set('writable_dirs', ['storage', 'bootstrap/cache']);
set('keep_releases', 5);
task('deploy:build_assets', function () {
runLocally('npm ci && npm run build');
upload('public/build', '{{release_path}}/public/build');
});
after('deploy:update_code', 'deploy:build_assets');
after('deploy:symlink_release', 'php-fpm:reload'); The php-fpm:reload task is non-negotiable when OPcache timestamp validation is disabled. Without it, users will see stale code until the next natural cache expiration. Deployer handles the symlink swap atomically, so there is zero downtime during the transition.
Automate TLS certificates with Certbot
Let’s Encrypt certificates expire every 90 days. Manual renewal is a ticking time bomb. Install Certbot and obtain certificates non-interactively:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos -m admin@example.com Certbot automatically configures Nginx SSL directives and sets up a systemd timer for renewal. Verify the timer is active with sudo systemctl list-timers | grep certbot. Test renewal without hitting rate limits using sudo certbot renew --dry-run. For multi-domain setups common in Nepal’s legal-tech sector (where firms often operate separate portals for marriage, divorce, and notary services), Certbot handles SAN certificates seamlessly.
How do you monitor and maintain production Ubuntu servers long-term?
Setup is a one-time event; maintenance is forever. The most common failure mode I encounter on client servers is not misconfiguration but neglect — outdated packages, full disks, expired certificates, and silently failing backup jobs. Build monitoring and maintenance into your infrastructure from day one, not as a reactive measure after an outage.
Implement automated updates safely
Unattended security patches prevent known exploits from lingering. Install and configure unattended-upgrades:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades Edit /etc/apt/apt.conf.d/50unattended-upgrades to allow only security updates, not feature upgrades that could break PHP extensions or Nginx modules. Application-level dependencies (Composer, npm) should be updated deliberately during scheduled maintenance windows, never automatically.
Set up log rotation and disk monitoring
Laravel logs, Nginx access logs, and MySQL slow query logs grow indefinitely without rotation. Ubuntu’s logrotate handles this, but verify configurations exist for your custom paths. More critically, set up disk space alerts before partitions fill. A simple cron job checking df -h and emailing warnings at 80% utilization has saved countless production incidents on projects I maintain. For teams managing multiple client sites across Nepal, centralized monitoring via Netdata or Prometheus provides visibility without per-server SSH sessions.
Schedule and verify backups
Backups that are never tested are not backups. Automate nightly MySQL dumps and file archives, then monthly restore tests to a staging environment. Store backups off-server — S3-compatible object storage costs pennies per gigabyte and survives total server failure. For legal-tech portals handling sensitive documents, encrypt backups at rest and maintain retention policies compliant with Nepal’s data handling expectations. Document the restore procedure; during an actual crisis, nobody remembers the exact tar flags or mysql import syntax they used six months ago.
Next Steps for Your Ubuntu Server Setup Guide Implementation
This Ubuntu Server Setup Guide provides the foundation, but production readiness requires validation against your specific workload. Test PHP-FPM pool sizing under realistic load before going live. Benchmark database queries with your actual dataset, not synthetic fixtures. Verify SSL renewal works end-to-end before trusting automation. If you are building a Laravel application for Nepal’s legal or e-commerce sector and need hands-on implementation support, reach out to discuss your infrastructure requirements. For developers evaluating whether to self-host or use managed platforms, my comparison of AWS cloud hosting versus shared hosting in Nepal breaks down the real cost and operational trade-offs. And if your application layer needs optimization alongside server tuning, the modern Laravel architecture best practices guide complements this infrastructure foundation with application-level patterns that reduce server resource demands.

