Setting up a production web server is one of the most fundamental skills for any Linux administrator. While managed hosting and cloud platforms have simplified deployment, understanding how to build a server from scratch gives you complete control over performance, security, and cost. This guide walks you through every step, from a fresh OS installation to a fully hardened, monitored production server.
Choosing Your Operating System
For production web servers in 2026, we recommend one of these distributions:
- Ubuntu Server 24.04 LTS β Wide community support, extensive package availability, 10 years of security updates
- AlmaLinux 9 β RHEL-compatible, enterprise-grade stability, ideal for organizations standardized on Red Hat
- Debian 12 (Bookworm) β Exceptional stability, minimal attack surface, preferred by security-conscious administrators
This guide uses Ubuntu Server 24.04 LTS for examples, with notes for AlmaLinux/RHEL where commands differ.
Step 1: Initial Server Setup
First Login and System Update
# Update package lists and upgrade all packages
sudo apt update && sudo apt upgrade -y
# Set timezone
sudo timedatectl set-timezone UTC
# Set hostname
sudo hostnamectl set-hostname webserver01
# Reboot to apply kernel updates
sudo reboot
Create a Non-Root Admin User
# Create a new admin user
sudo adduser deployer
# Add to sudo group
sudo usermod -aG sudo deployer
# Set up SSH key authentication for the new user
sudo mkdir -p /home/deployer/.ssh
sudo cp ~/.ssh/authorized_keys /home/deployer/.ssh/
sudo chown -R deployer:deployer /home/deployer/.ssh
sudo chmod 700 /home/deployer/.ssh
sudo chmod 600 /home/deployer/.ssh/authorized_keys
Step 2: SSH Hardening
SSH is your primary access method, so securing it properly is critical:
# Edit SSH configuration
sudo nano /etc/ssh/sshd_config
Apply these settings:
# Disable root login
PermitRootLogin no
# Disable password authentication (use keys only)
PasswordAuthentication no
# Change default port (optional but reduces automated scans)
Port 2222
# Limit login attempts
MaxAuthTries 3
# Set idle timeout (10 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2
# Restrict to specific users
AllowUsers deployer
# Use only strong algorithms
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Restart SSH (keep your current session open!)
sudo systemctl restart sshd
# Test the new configuration from another terminal before closing this one
Step 3: Firewall Configuration
# Install and enable UFW
sudo apt install ufw -y
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (use your custom port if changed)
sudo ufw allow 2222/tcp comment "SSH"
# Allow HTTP and HTTPS
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"
# Enable the firewall
sudo ufw enable
# Verify rules
sudo ufw status verbose
Step 4: Install and Configure NGINX
# Install NGINX
sudo apt install nginx -y
# Start and enable
sudo systemctl start nginx
sudo systemctl enable nginx
# Verify installation
curl -I http://localhost
Optimize NGINX Configuration
# Edit main configuration
sudo nano /etc/nginx/nginx.conf
Key optimizations:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Create a Virtual Host
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.php;
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 5: SSL Certificate with Let's Encrypt
# Install Certbot
sudo apt install certbot python3-certbot-nginx -y
# Obtain and install certificate
sudo certbot --nginx -d example.com -d www.example.com \
--non-interactive --agree-tos --email admin@example.com
# Verify auto-renewal
sudo certbot renew --dry-run
# The timer for auto-renewal is set up automatically
sudo systemctl status certbot.timer
Step 6: Install Fail2ban
# Install fail2ban
sudo apt install fail2ban -y
# Create local configuration
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
banaction = ufw
[sshd]
enabled = true
port = 2222
maxretry = 3
[nginx-http-auth]
enabled = true
[nginx-limit-req]
enabled = true
logpath = /var/log/nginx/*error.log
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo fail2ban-client status
Step 7: Automatic Security Updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
Step 8: Basic Monitoring
# Install monitoring tools
sudo apt install htop iotop nethogs logwatch -y
# Set up logwatch for daily reports
sudo nano /etc/cron.daily/00logwatch
#!/bin/bash
/usr/sbin/logwatch --output mail --mailto admin@example.com --detail high
Step 9: Backup Configuration
# Create backup script
sudo nano /usr/local/bin/backup.sh
#!/bin/bash
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Backup NGINX configuration
tar czf "$BACKUP_DIR/nginx.tar.gz" /etc/nginx/
# Backup website files
tar czf "$BACKUP_DIR/www.tar.gz" /var/www/
# Backup SSL certificates
tar czf "$BACKUP_DIR/letsencrypt.tar.gz" /etc/letsencrypt/
# Remove backups older than 30 days
find /backup -type d -mtime +30 -exec rm -rf {} +
echo "Backup completed: $BACKUP_DIR"
sudo chmod +x /usr/local/bin/backup.sh
# Add to cron for daily execution at 2 AM
echo "0 2 * * * root /usr/local/bin/backup.sh" | sudo tee /etc/cron.d/daily-backup
Post-Setup Verification Checklist
- SSH access works with key authentication only
- Root login is disabled
- Firewall is active with only necessary ports open
- NGINX is serving your site with HTTPS
- SSL certificate is valid and auto-renewal works
- Fail2ban is protecting against brute force
- Automatic security updates are enabled
- Backups are configured and tested
- Monitoring is in place for resource usage and logs
Recommended Reading
Build on your web server skills with these comprehensive Dargslan guides:
- Linux Web Server Setup β The complete guide to production web server deployment
- NGINX Fundamentals β Master NGINX configuration and optimization
- Apache Fundamentals β Complete guide to the Apache HTTP Server
- Linux Security Hardening β Advanced security techniques for Linux servers