Dedicated Server Setup, Security & Optimization Guide (2026)

Whether you just received your first dedicated server or you are migrating from shared hosting, this guide covers every critical phase from scratch: initial Linux setup, SSH hardening, firewall configuration, web server installation and performance tuning, MySQL/MariaDB optimization, automated backups, and real-time monitoring.

All commands are tested on Ubuntu 22.04 LTS and CentOS Stream 9. Follow each step in order and your server will be production-ready by the end.

Prerequisites

Before starting, make sure you have the following ready:

🖥️
Operating System

Ubuntu 22.04 LTS or CentOS Stream 9 (recommended).

🔑
Root Access

You will need sudo or root privileges to run the commands in this guide.

⚙️
Hardware

Minimum: 2 CPU cores, 4 GB RAM, 50 GB NVMe SSD storage.

🌐
SSH Client

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.

Step 01

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:

bash
ssh root@YOUR_SERVER_IP

Once connected, update all installed packages (Ubuntu):

bash — Ubuntu 22.04
apt update && apt upgrade -y

Or on CentOS Stream 9:

bash — CentOS Stream 9
dnf update -y

Set a meaningful hostname so you can identify your server at a glance:

bash
hostnamectl set-hostname srv01.yourdomain.com
hostnamectl

Finally, set the correct timezone for your region:

bash
# List available timezones
timedatectl list-timezones | grep Asia

# Set your timezone (replace with your own region)
timedatectl set-timezone Asia/Colombo
timedatectl status
Step 02

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:

bash
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:

bash — Run on your LOCAL machine
# 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:

/etc/ssh/sshd_config — key settings to change sshd_config
plaintext
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:

bash
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.

Step 03

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:

bash — 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:

bash — CentOS
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.

Step 04

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.

bash
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:

/etc/fail2ban/jail.local — recommended settings fail2ban
plaintext
[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:

bash
systemctl enable --now fail2ban
fail2ban-client status
fail2ban-client status sshd
Step 05

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.

/etc/sysctl.d/99-hardening.conf — create this file sysctl
plaintext
# 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:

bash
sysctl --system
Step 06

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:

bash
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:

/etc/nginx/nginx.conf — optimized for production nginx.conf
nginx
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:

bash
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:

nginx — add to http block
# 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_processes1autoUses all CPU cores
worker_connections7684096More concurrent connections
gzipoffon, level 5~70% smaller responses
keepalive_timeout75s65sBalanced connection reuse
server_tokensonoffHides version from attackers
Step 07

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:

bash
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:

sql
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:

/etc/mysql/conf.d/performance.cnf — tuned for 8 GB+ RAM my.cnf
plaintext
[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
bash
systemctl restart mariadb

# Use MySQLTuner for automated recommendations
apt install mysqltuner -y
mysqltuner
Step 08

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:

/etc/security/limits.conf — append at end of file limits.conf
plaintext
* soft    nofile    65535
* hard    nofile    65535
root      soft    nofile    65535
root      hard    nofile    65535

Tune the network stack for high-traffic web servers:

/etc/sysctl.d/99-network-performance.conf — create this file sysctl
plaintext
# 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
bash
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.):

bash
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
Step 09

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:

bash
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):

/usr/local/bin/server-backup.sh bash script
bash
#!/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:

bash
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:

bash
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.

Step 10

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
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:

/usr/local/bin/health-check.sh bash script
bash
#!/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"
bash
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 Usagetop / htop< 80% sustained
Memory Usagefree -h< 85%, no swap usage
Disk Usagedf -h< 80% used
Disk I/O Waitiostat -x 2< 20% wa
Load Averageuptime< number of CPU cores
MySQL Connectionsmysqladmin status< 80% of max_connections
Failed Servicessystemctl --failedZero
Disk Errorsdmesg | grep errorZero
Production Readiness

Production Readiness Checklist

Before considering your server production-ready, verify every item below is complete. Tick them off as you go.

Initial Setup

All system packages updated
Hostname correctly set
Timezone configured
Non-root admin user created
SSH key authentication enabled
Root SSH login disabled
SSH port changed from 22
Password auth disabled via SSH

Security

UFW or Firewalld enabled
Only required ports open
Fail2Ban protecting SSH
Automatic security updates on
sysctl kernel hardening applied
Unnecessary services disabled

Web Server & Database

Nginx installed and running
SSL certificate installed
HTTPS redirect configured
Gzip compression enabled
MariaDB secured and tuned
App uses dedicated DB user
Slow query log enabled
Redis cache running

Backup & Monitoring

Daily backup script running
Off-site rsync configured
Backup restoration tested
Netdata monitoring installed
Daily health check email set
Log rotation verified

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:

bash
ufw allow 2222/tcp
ufw reload

Nginx fails to start — port already in use

bash
# 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:

bash
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

bash
# 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.