Prerequisites
Before starting, make sure you have the following ready:
Ubuntu 22.04 LTS or CentOS Stream 9 (recommended).
You will need sudo or root privileges to run the commands in this guide.
Minimum: 2 CPU cores, 4 GB RAM, 50 GB NVMe SSD storage.
Terminal (Linux/macOS) or PuTTY / Windows Terminal on Windows.
This guide does not cover virtualization or containers. All steps apply directly to a bare-metal dedicated server running Linux.
Connect via SSH & Update the System
Your server arrives with a temporary root password from FitServers. The very first thing to do is connect, update all packages, and set a proper hostname. A fully updated system closes known security vulnerabilities before you do anything else.
Connect from your local machine:
ssh root@YOUR_SERVER_IP
Once connected, update all installed packages (Ubuntu):
apt update && apt upgrade -y
Or on CentOS Stream 9:
dnf update -y
Set a meaningful hostname so you can identify your server at a glance:
hostnamectl set-hostname srv01.yourdomain.com
hostnamectl
Finally, set the correct timezone for your region:
# List available timezones
timedatectl list-timezones | grep Asia
# Set your timezone (replace with your own region)
timedatectl set-timezone Asia/Colombo
timedatectl status
Create a Non-Root Admin User & Set Up SSH Keys
Running everything as root is a major security risk. We create a dedicated admin user, grant it sudo privileges, configure SSH key authentication, and then disable root login and password-based login entirely.
Create the admin user and add it to the sudo group:
adduser admin
# Follow the prompts to set a password
usermod -aG sudo admin
# Verify sudo access
su - admin
sudo whoami # should output: root
Now generate an SSH key pair on your local machine (not the server) and copy the public key over:
# Generate a modern Ed25519 key pair
ssh-keygen -t ed25519 -C "admin@fitservers"
# Copy your public key to the server
ssh-copy-id admin@YOUR_SERVER_IP
# Test key-based login BEFORE disabling passwords
ssh admin@YOUR_SERVER_IP
Once confirmed working, harden the SSH daemon configuration:
Port 2222 # Change from default 22
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
AllowUsers admin
X11Forwarding no
Apply the new SSH configuration:
sudo systemctl restart sshd
Critical: Keep your existing SSH session open while testing the new settings in a second terminal window. If you accidentally lock yourself out, use the FitServers remote console (IPMI/KVM) to recover access.
Configure the Firewall (UFW & Firewalld)
An unprotected server exposed to the internet will be probed within minutes of going online. UFW (Uncomplicated Firewall) is the recommended firewall manager for Ubuntu. Always configure rules before enabling to avoid locking yourself out.
Install and configure UFW on Ubuntu:
apt install ufw -y
# Default policies — deny all incoming, allow all outgoing
ufw default deny incoming
ufw default allow outgoing
# Allow your custom SSH port (replace 2222 with yours)
ufw allow 2222/tcp
# Allow HTTP and HTTPS
ufw allow 80/tcp
ufw allow 443/tcp
# Enable and verify
ufw enable
ufw status verbose
On CentOS Stream 9 use Firewalld:
systemctl status firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --reload
Always ensure your custom SSH port is allowed before enabling the firewall. Locking yourself out of port 22 with no alternative access will require a remote console session to fix.
Install & Configure Fail2Ban
Fail2Ban monitors your log files and automatically bans IP addresses that show signs of brute-force activity. It is essential for any server with SSH or web services exposed to the internet.
apt install fail2ban -y
# Create a local override — never edit jail.conf directly
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Open /etc/fail2ban/jail.local and update the key settings:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport
[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
maxretry = 3
Enable and verify Fail2Ban:
systemctl enable --now fail2ban
fail2ban-client status
fail2ban-client status sshd
Kernel Hardening with sysctl
Linux kernel parameters can be tuned to dramatically reduce your server's attack surface and improve network security. Create a dedicated sysctl config file to keep your changes organized and persistent across reboots.
# Disable IP source routing
net.ipv4.conf.all.accept_source_route = 0
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Log packets with impossible source addresses
net.ipv4.conf.all.log_martians = 1
# Disable IPv6 if not required
net.ipv6.conf.all.disable_ipv6 = 1
Apply the changes immediately without rebooting:
sysctl --system
Install & Optimize Nginx (Web Server)
Nginx is the most popular web server for high-performance dedicated server deployments. The default configuration is conservative — the following steps install Nginx, apply a production-tuned configuration, enable HTTPS with a free Let's Encrypt SSL certificate, and add rate limiting for DDoS protection.
Install Nginx:
apt install nginx -y
systemctl enable --now nginx
# Confirm it is running
systemctl status nginx
curl -I http://localhost
Apply a production-optimized /etc/nginx/nginx.conf:
worker_processes auto; # Matches CPU core count
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll; # Best event model for Linux
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression — reduces response size by ~70%
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml
application/xml+rss text/javascript;
# Security — hide Nginx version from response headers
server_tokens off;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Buffer tuning
client_body_buffer_size 128k;
client_max_body_size 50m;
client_header_buffer_size 1k;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Install Certbot and get a free SSL certificate from Let's Encrypt:
apt install certbot python3-certbot-nginx -y
# Obtain and install certificate automatically
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Verify auto-renewal is working
certbot renew --dry-run
systemctl status certbot.timer
Add rate limiting to your server block to protect against DDoS and abuse:
# In nginx.conf http block:
limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m;
# In your server block:
limit_req zone=one burst=10 nodelay;
Nginx Performance Quick Reference
| Setting | Default | Optimized | Impact |
|---|---|---|---|
worker_processes | 1 | auto | Uses all CPU cores |
worker_connections | 768 | 4096 | More concurrent connections |
gzip | off | on, level 5 | ~70% smaller responses |
keepalive_timeout | 75s | 65s | Balanced connection reuse |
server_tokens | on | off | Hides version from attackers |
Install, Secure & Optimize MySQL / MariaDB
Database performance is often the primary bottleneck on a dedicated server. MariaDB is a drop-in replacement for MySQL and is recommended for most use cases due to better performance and active community support.
Install MariaDB:
apt install mariadb-server mariadb-client -y
systemctl enable --now mariadb
# Run the interactive security hardening script
mysql_secure_installation
During mysql_secure_installation: set a strong root password, remove anonymous users, disallow remote root login, and remove the test database. Answer Yes to every security prompt.
Create a dedicated database and user for your application. Never use root for day-to-day application connections:
mysql -u root -p
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Tune MariaDB for production by creating a performance config file. For a server with 8 GB RAM, use these settings:
[mysqld]
# InnoDB Buffer Pool — most critical setting
# Set to 70-80% of available RAM for dedicated DB servers
innodb_buffer_pool_size = 6G
innodb_buffer_pool_instances = 6
# InnoDB I/O — set io_capacity higher for NVMe SSDs
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_log_file_size = 512M
# Connection settings
max_connections = 300
thread_cache_size = 50
table_open_cache = 4000
# Temp tables
tmp_table_size = 256M
max_heap_table_size = 256M
# Slow query log — identify bottlenecks
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
systemctl restart mariadb
# Use MySQLTuner for automated recommendations
apt install mysqltuner -y
mysqltuner
Server Performance Optimization (OS-Level)
Beyond the web server and database, the operating system itself has tunable parameters that significantly impact overall server performance. These settings raise open file limits, optimize the TCP/IP network stack, and enable BBR — Google's modern TCP congestion control algorithm.
Raise the open file limits to prevent "too many open files" errors on busy servers:
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
Tune the network stack for high-traffic web servers:
# Increase socket buffer sizes
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.netdev_max_backlog = 5000
# TCP buffer tuning
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Increase connection backlog
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# Enable BBR congestion control (recommended for web servers)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
sysctl --system
# Confirm BBR is active
sysctl net.ipv4.tcp_congestion_control
Install and configure Redis as an object cache to dramatically speed up PHP applications (WordPress, Magento, Laravel, etc.):
apt install redis-server -y
systemctl enable --now redis-server
# Set max memory and eviction policy
redis-cli CONFIG SET maxmemory 512mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# Test Redis is responding
redis-cli ping # should return: PONG
Automated Server Backup with cron
A backup you have never tested is not a backup. This step creates a comprehensive shell script that backs up all MySQL databases and web files, schedules it nightly via cron, and replicates backups off-site using rsync over SSH.
Create the backup directory and script:
mkdir -p /backups/{databases,files}
nano /usr/local/bin/server-backup.sh
Paste the full backup script (replace YOUR_DB_PASSWORD with your root MariaDB password):
#!/bin/bash
# FitServers — Automated Backup Script
BACKUP_DIR="/backups"
DB_USER="root"
DB_PASS="YOUR_DB_PASSWORD"
WEB_DIR="/var/www"
RETENTION_DAYS=14
DATE=$(date +%Y%m%d_%H%M%S)
echo "[$(date)] Starting backup..."
# -- Database backup --
DATABASES=$(mysql -u"$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" | \
grep -Ev "(Database|information_schema|performance_schema|sys|mysql)")
for DB in $DATABASES; do
mysqldump -u"$DB_USER" -p"$DB_PASS" \
--single-transaction --quick --lock-tables=false \
"$DB" | gzip > "$BACKUP_DIR/databases/${DB}_${DATE}.sql.gz"
echo " ✓ Database backed up: $DB"
done
# -- Web files backup --
tar -czf "$BACKUP_DIR/files/webfiles_${DATE}.tar.gz" \
--exclude="$WEB_DIR/*/cache" \
--exclude="$WEB_DIR/*/tmp" \
"$WEB_DIR"
echo " ✓ Web files backed up"
# -- Remove backups older than retention period --
find "$BACKUP_DIR" -name "*.gz" -mtime +"$RETENTION_DAYS" -delete
echo " ✓ Old backups removed (> $RETENTION_DAYS days)"
echo "[$(date)] Backup complete!"
Make the script executable and test it:
chmod +x /usr/local/bin/server-backup.sh
# Test run manually
sudo /usr/local/bin/server-backup.sh
# Verify output
ls -lh /backups/databases/
ls -lh /backups/files/
Schedule the backup and an off-site rsync transfer with cron:
crontab -e
# Add these two lines:
# Daily local backup at 2:30 AM
30 2 * * * /usr/local/bin/server-backup.sh >> /var/log/backup.log 2>&1
# Daily off-site rsync at 3:00 AM (replace with your backup server)
0 3 * * * rsync -avz --delete /backups/ [email protected]:/remote/srv01/
Always restore from your backups on a test server at least once per quarter to verify they actually work. A backup you have never tested is not a backup.
Server Monitoring & Health Alerts
You cannot manage what you cannot measure. Install Netdata for real-time dashboards and set up a lightweight daily health check email so you are always aware of your server's state.
Install Netdata (provides beautiful real-time dashboards for CPU, RAM, disk, network, MySQL, Nginx, and more):
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
# Netdata listens on port 19999 — block it from public access
ufw deny 19999
# Access securely via SSH tunnel from your local machine:
ssh -L 19999:localhost:19999 admin@YOUR_SERVER_IP
# Then open: http://localhost:19999 in your browser
Create a lightweight daily health check script that emails you a server summary every morning:
#!/bin/bash
HOSTNAME=$(hostname)
EMAIL="[email protected]"
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}')
MEM=$(free -h | awk '/^Mem/{print $3 "/" $2}')
DISK=$(df -h / | awk 'NR==2{print $5 " (" $3 "/" $2 ")"}')
UPTIME=$(uptime -p)
LOAD=$(cat /proc/loadavg | awk '{print $1, $2, $3}')
FAILED=$(systemctl --failed --no-pager | tail -n +2 | head -n -6)
REPORT="Server Health: $HOSTNAME
=================================
Date: $(date)
Uptime: $UPTIME
Load: $LOAD
CPU: $CPU%
Memory: $MEM
Disk: $DISK
Failed Services:
$FAILED
================================="
echo "$REPORT" | mail -s "[$HOSTNAME] Daily Health Check" "$EMAIL"
chmod +x /usr/local/bin/health-check.sh
# Schedule daily at 7:00 AM
crontab -e
# Add: 0 7 * * * /usr/local/bin/health-check.sh
Essential Monitoring Metrics
| Metric | Command | Healthy Threshold |
|---|---|---|
| CPU Usage | top / htop | < 80% sustained |
| Memory Usage | free -h | < 85%, no swap usage |
| Disk Usage | df -h | < 80% used |
| Disk I/O Wait | iostat -x 2 | < 20% wa |
| Load Average | uptime | < number of CPU cores |
| MySQL Connections | mysqladmin status | < 80% of max_connections |
| Failed Services | systemctl --failed | Zero |
| Disk Errors | dmesg | grep error | Zero |
Production Readiness Checklist
Before considering your server production-ready, verify every item below is complete. Tick them off as you go.
Initial Setup
Security
Web Server & Database
Backup & Monitoring
Troubleshooting Common Issues
Cannot connect via SSH after changing the port
If you changed the SSH port to 2222 but forgot to open it in UFW, you will be locked out. Access the server through the FitServers IPMI/remote console and run:
ufw allow 2222/tcp
ufw reload
Nginx fails to start — port already in use
# Find what process is occupying port 80
ss -tlnp | grep :80
lsof -i :80
# Kill it and start Nginx
fuser -k 80/tcp
systemctl start nginx
MariaDB refuses to start after changing innodb_buffer_pool_size
If the buffer pool size exceeds available RAM, MariaDB will refuse to start. Check the error log and reduce the value:
tail -50 /var/log/mysql/error.log
# Check how much RAM is available
free -h
# Edit and lower the buffer pool size, then restart
nano /etc/mysql/conf.d/performance.cnf
systemctl restart mariadb
Disk full — server unresponsive
# Find top space consumers
df -h
du -hx / | sort -rh | head -20
# Clear systemd journal logs (keep last 200MB)
journalctl --vacuum-size=200M
# Clear apt cache
apt clean
Frequently Asked Questions (FAQ)
Is Ubuntu or CentOS better for a dedicated server?
Both are excellent choices. Ubuntu 22.04 LTS has a larger community, more up-to-date packages, and is generally recommended for web servers and developers new to Linux. CentOS Stream 9 (and its enterprise equivalent AlmaLinux/Rocky Linux) is preferred in corporate environments and for applications that require long-term binary compatibility. For most FitServers customers, Ubuntu 22.04 LTS is the recommended starting point.
Do I need all 10 steps for a basic website?
For a basic website, Steps 1 through 6 (initial setup, SSH hardening, firewall, Fail2Ban, kernel tuning, and Nginx with SSL) are the minimum you should complete before going live. Steps 7 through 10 become important as soon as you add a database, handle significant traffic, or need to guarantee data safety through backups.
How much RAM should I allocate to the InnoDB buffer pool?
The general rule is to set innodb_buffer_pool_size to 70–80% of total available RAM on a server dedicated to running MySQL or MariaDB. On a shared server (web + database on the same machine), set it to 40–50% of RAM to leave headroom for Nginx, PHP-FPM, and the OS itself. Always monitor with MySQLTuner after making changes.
How often should I test my backups?
At a minimum, perform a full restore test once per quarter. For business-critical data, test monthly. A backup that has never been tested through a full restore procedure cannot be relied upon in an emergency. The restore process also reveals any gaps in your backup coverage that you may have missed.
What is BBR and should I always enable it?
BBR (Bottleneck Bandwidth and Round-trip propagation time) is a TCP congestion control algorithm developed by Google. It significantly improves throughput and latency over long-distance or lossy network paths. For web servers and content delivery, enabling BBR is almost always beneficial and safe. It is available in Linux kernels 4.9 and later, which covers Ubuntu 22.04 and CentOS Stream 9.
Can I run Nginx and Apache on the same server?
Yes, but they cannot both listen on port 80 and 443 simultaneously. The most common setup is to use Nginx as a reverse proxy on port 80/443, which forwards requests to Apache running on an internal port (such as 8080). This gives you Nginx's performance advantages for serving static files and handling SSL termination, while keeping Apache's .htaccess and mod_php compatibility for legacy PHP applications.
Why Choose Fit Servers Dedicated Servers?
Every step in this guide is designed to squeeze maximum performance and security out of your dedicated server. But your results also depend heavily on the hardware you start with. FitServers dedicated servers are built for exactly this kind of setup:
- High Performance Hardware: Latest AMD EPYC CPUs, NVMe SSDs, 128–512 GB DDR5 RAM — ideal for high-traffic web applications and demanding database workloads.
- DDoS Protection: Up to 10 Tbps mitigation with 10 Gbps unmetered bandwidth so your Fail2Ban and rate-limiting rules are the last line of defence, not the first.
- Instant Provisioning: Get your server online in minutes with a clean Ubuntu 22.04 or CentOS Stream 9 image and start following this guide right away.
- 99.99% Uptime Guarantee: Redundant power and network infrastructure ensures your production server stays online.
- 24/7 Expert Support: Our team is always available to help with server configuration, troubleshooting, and hardware issues.
Get Your Dedicated Server at Fit Servers — Start with the right hardware and make every step of this guide count.
Discover fitservers Dedicated Server Locations
FitServers dedicated servers are available around the world, providing diverse options for deploying your Linux server. Each region offers unique advantages, making it easier to choose a location closest to your users for the lowest possible latency.