Linux Web Server Setup

Created by brian on

Debian Web Server Setup Guide

A comprehensive guide for setting up and hardening a new Debian/Ubuntu web server.

Table of Contents


Prerequisites

  • Fresh Debian or Ubuntu server installation
  • Root access or sudo privileges
  • SSH access to the server
  • Basic familiarity with Linux command line

Initial Setup

System Updates

Always update the system before installing new packages:

bash
sudo apt update sudo apt upgrade -y sudo apt autoremove -y

Timezone Configuration

Set your server's timezone:

bash
sudo timedatectl set-timezone America/New_York

List available timezones:

bash
timedatectl list-timezones

User Management

Add new user:

bash
sudo useradd -m -s /bin/bash USERNAME sudo passwd USERNAME

Add user to sudo group:

bash
sudo usermod -aG sudo USERNAME

Copy SSH keys to new user:

Before disabling root login, ensure your new user can SSH in:

bash
sudo mkdir -p /home/USERNAME/.ssh sudo cp ~/.ssh/authorized_keys /home/USERNAME/.ssh/ sudo chown -R USERNAME:USERNAME /home/USERNAME/.ssh sudo chmod 700 /home/USERNAME/.ssh sudo chmod 600 /home/USERNAME/.ssh/authorized_keys

SSH Hardening

Change SSH port and disable root login:

bash
sudo nano /etc/ssh/sshd_config

Update the following settings:

bash
Port 2222 PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes

Important: Make sure you've set up SSH keys and tested login with your new user before applying these changes.

Restart SSH service:

bash
sudo systemctl restart sshd

Hostname Configuration

Change hostname:

bash
sudo nano /etc/hostname sudo nano /etc/hosts

Update /etc/hosts to include your new hostname:

127.0.0.1       localhost
127.0.1.1       your-hostname

# IPv6 entries
::1             localhost ip6-localhost ip6-loopback

Apply changes:

bash
sudo hostnamectl set-hostname your-hostname

Security

Uncomplicated Firewall

Install UFW:

bash
sudo apt install ufw

Setup defaults:

bash
sudo ufw default deny incoming sudo ufw default allow outgoing

Allow SSH (use your custom port if changed):

bash
sudo ufw allow 2222/tcp sudo ufw status verbose

Important: If you changed your SSH port, make sure to allow that port instead of 22.

Allow web traffic:

bash
sudo ufw allow 'Nginx Full'

Or manually:

bash
sudo ufw allow 80/tcp sudo ufw allow 443/tcp

Enable UFW:

bash
sudo ufw enable

Important: Make sure you've allowed your SSH port before enabling UFW to avoid being locked out.

Other useful UFW commands:

View rules with numbers:

bash
sudo ufw status numbered

Delete a rule:

bash
sudo ufw delete NUMBER

Allow port ranges:

bash
sudo ufw allow 1111:1115/tcp sudo ufw allow 1116:1119/udp

Disable UFW:

bash
sudo ufw disable

Fail2ban

Fail2ban protects against brute-force attacks by banning IPs that show malicious signs.

Install fail2ban:

bash
sudo apt install fail2ban

Review default configuration:

bash
sudo nano /etc/fail2ban/jail.conf

Don't edit this file directly. Instead, create a local override:

Create local configuration:

bash
sudo nano /etc/fail2ban/jail.local

Add the following configuration:

bash
[DEFAULT] bantime = 3600 findtime = 600 maxretry = 4 destemail = your-email@example.com sendername = Fail2Ban action = %(action_mw)s [sshd] enabled = true port = 2222 logpath = %(sshd_log)s backend = %(sshd_backend)s [nginx-http-auth] enabled = true filter = nginx-http-auth port = http,https logpath = /var/log/nginx/error.log [nginx-noscript] enabled = true port = http,https filter = nginx-noscript logpath = /var/log/nginx/access.log [nginx-badbots] enabled = true port = http,https filter = nginx-badbots logpath = /var/log/nginx/access.log

Start and enable fail2ban:

bash
sudo systemctl start fail2ban sudo systemctl enable fail2ban

Reload fail2ban after config changes:

bash
sudo fail2ban-client reload

Check fail2ban status:

bash
sudo fail2ban-client status sudo fail2ban-client status sshd

Unban an IP address:

bash
sudo fail2ban-client set sshd unbanip IP_ADDRESS

Automatic Security Updates

Configure unattended upgrades for security patches:

bash
sudo apt install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades

Edit the configuration:

bash
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Enable automatic updates:

bash
sudo systemctl enable unattended-upgrades sudo systemctl start unattended-upgrades

Web Server

Nginx

Install Nginx:

bash
sudo apt install nginx

Start and enable Nginx:

bash
sudo systemctl start nginx sudo systemctl enable nginx

Create a site configuration:

bash
sudo nano /etc/nginx/sites-available/example.com.conf

Basic configuration example:

nginx
server { listen 80; listen [::]:80; server_name example.com www.example.com; root /var/www/example.com; index index.html index.htm; location / { try_files $uri $uri/ =404; } }

Enable a site configuration:

bash
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/

Disable the default site:

bash
sudo rm /etc/nginx/sites-enabled/default

Test Nginx configuration:

bash
sudo nginx -t

Reload Nginx:

bash
sudo systemctl reload nginx

Restart Nginx:

bash
sudo systemctl restart nginx

Check Nginx status:

bash
sudo systemctl status nginx

SSL Certificates

Official documentation: Certbot Instructions

Obtain SSL certificate:

For automatic Nginx configuration:

bash
sudo certbot --nginx -d example.com -d www.example.com

Or use certonly mode (manual configuration):

bash
sudo certbot certonly --nginx -d example.com -d www.example.com

Test auto-renewal:

Certbot installed via snap automatically sets up renewal. Test it:

bash
sudo certbot renew --dry-run

Verify auto-renewal timer:

Check that the snap timer is active:

bash
sudo snap list certbot sudo systemctl list-timers | grep certbot

The snap installation automatically handles renewal twice daily.

Check certificate expiration:

bash
sudo certbot certificates

Renew certificates manually (if needed):

bash
sudo certbot renew

Development Tools

Git

Install Git:

bash
sudo apt install git

Configure Git:

bash
git config --global user.name "Your Name" git config --global user.email "your.email@example.com"

Setup GitHub SSH:

Generate SSH key:

bash
ssh-keygen -t ed25519 -C "your_email@example.com"

Start SSH agent:

bash
eval "$(ssh-agent -s)"

Create or edit SSH config:

bash
nano ~/.ssh/config

Add:

bash
Host github.com User git Hostname github.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_ed25519

Add SSH key to agent:

bash
ssh-add ~/.ssh/id_ed25519

Display public key (add this to GitHub):

bash
cat ~/.ssh/id_ed25519.pub

Test connection:

bash
ssh -T git@github.com

Node Version Manager

Official repository: nvm-sh/nvm

Install build dependencies:

bash
sudo apt update sudo apt install build-essential libssl-dev

Install NVM:

bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

Or using wget:

bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

Load NVM:

Close and reopen your terminal, or run:

bash
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

List available Node versions:

bash
nvm ls-remote

Install latest LTS version:

bash
nvm install --lts nvm use --lts

Install specific version:

bash
nvm install 20.10.0 nvm use 20.10.0

Set default Node version:

bash
nvm alias default 20.10.0

List installed versions:

bash
nvm ls

Alternative: Consider fnm for a faster, Rust-based Node version manager.

Docker

Official documentation: Docker Engine Install

Install Docker:

bash
sudo apt update sudo apt install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc

Add Docker repository:

bash
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker packages:

bash
sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Add user to docker group:

This allows running Docker commands without sudo, which is necessary for cron jobs:

bash
sudo usermod -aG docker $USER

Important: Log out and log back in for the group changes to take effect, or run:

bash
newgrp docker

Verify Docker installation:

bash
docker run hello-world

Enable Docker to start on boot:

bash
sudo systemctl enable docker sudo systemctl enable containerd

Basic Docker commands:

List running containers:

bash
docker ps

List all containers:

bash
docker ps -a

View Docker images:

bash
docker images

Stop a container:

bash
docker stop CONTAINER_ID

Remove a container:

bash
docker rm CONTAINER_ID

Docker Compose:

Docker Compose is included with the installation above. Verify:

bash
docker compose version

Start services defined in docker-compose.yml:

bash
docker compose up -d

Stop services:

bash
docker compose down

Using Docker in cron jobs:

Since your user is in the docker group, you can run Docker commands in cron jobs:

bash
crontab -e

Example cron job:

bash
0 2 * * * /usr/bin/docker exec my-container /path/to/backup-script.sh

Note: Use full paths in cron jobs (/usr/bin/docker instead of just docker).


Process Management

PM2

PM2 is a production process manager for Node.js applications.

Install PM2 globally:

bash
npm install -g pm2

Start an application:

bash
pm2 start app.js --name myapp

List applications:

bash
pm2 list

Monitor applications:

bash
pm2 monit

View logs:

bash
pm2 logs pm2 logs myapp

Restart application:

bash
pm2 restart myapp

Stop application:

bash
pm2 stop myapp

Setup PM2 startup script:

bash
pm2 startup systemd

Run the command that PM2 outputs, then:

bash
pm2 save

Configuration file example:

Create ecosystem.config.js:

javascript
module.exports = { apps: [ { name: 'myapp', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, }, ], };

Start with config:

bash
pm2 start ecosystem.config.js

Monitoring & Utilities

Essential tools:

bash
sudo apt install htop ncdu net-tools curl wget
  • htop: Interactive process viewer
  • ncdu: Disk usage analyzer
  • net-tools: Network tools (netstat, ifconfig)

Check disk usage:

bash
df -h ncdu /

Check memory usage:

bash
free -h

Check running processes:

bash
htop

View system logs:

bash
sudo journalctl -xe sudo journalctl -u nginx

Check open ports:

bash
sudo netstat -tulpn sudo ss -tulpn

Last updated

>

Comments (0)

Loading...

Join the conversation!

Sign up for free to leave a comment.