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
- Initial Setup
- Security
- Web Server
- Development Tools
- Process Management
- Monitoring & Utilities
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:
bashsudo apt update sudo apt upgrade -y sudo apt autoremove -y
Timezone Configuration
Set your server's timezone:
bashsudo timedatectl set-timezone America/New_York
List available timezones:
bashtimedatectl list-timezones
User Management
Add new user:
bashsudo useradd -m -s /bin/bash USERNAME sudo passwd USERNAME
Add user to sudo group:
bashsudo usermod -aG sudo USERNAME
Copy SSH keys to new user:
Before disabling root login, ensure your new user can SSH in:
bashsudo 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:
bashsudo nano /etc/ssh/sshd_config
Update the following settings:
bashPort 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:
bashsudo systemctl restart sshd
Hostname Configuration
Change hostname:
bashsudo 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:
bashsudo hostnamectl set-hostname your-hostname
Security
Uncomplicated Firewall
Install UFW:
bashsudo apt install ufw
Setup defaults:
bashsudo ufw default deny incoming sudo ufw default allow outgoing
Allow SSH (use your custom port if changed):
bashsudo 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:
bashsudo ufw allow 'Nginx Full'
Or manually:
bashsudo ufw allow 80/tcp sudo ufw allow 443/tcp
Enable UFW:
bashsudo 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:
bashsudo ufw status numbered
Delete a rule:
bashsudo ufw delete NUMBER
Allow port ranges:
bashsudo ufw allow 1111:1115/tcp sudo ufw allow 1116:1119/udp
Disable UFW:
bashsudo ufw disable
Fail2ban
Fail2ban protects against brute-force attacks by banning IPs that show malicious signs.
Install fail2ban:
bashsudo apt install fail2ban
Review default configuration:
bashsudo nano /etc/fail2ban/jail.conf
Don't edit this file directly. Instead, create a local override:
Create local configuration:
bashsudo 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:
bashsudo systemctl start fail2ban sudo systemctl enable fail2ban
Reload fail2ban after config changes:
bashsudo fail2ban-client reload
Check fail2ban status:
bashsudo fail2ban-client status sudo fail2ban-client status sshd
Unban an IP address:
bashsudo fail2ban-client set sshd unbanip IP_ADDRESS
Automatic Security Updates
Configure unattended upgrades for security patches:
bashsudo apt install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit the configuration:
bashsudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Enable automatic updates:
bashsudo systemctl enable unattended-upgrades sudo systemctl start unattended-upgrades
Web Server
Nginx
Install Nginx:
bashsudo apt install nginx
Start and enable Nginx:
bashsudo systemctl start nginx sudo systemctl enable nginx
Create a site configuration:
bashsudo nano /etc/nginx/sites-available/example.com.conf
Basic configuration example:
nginxserver { 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:
bashsudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
Disable the default site:
bashsudo rm /etc/nginx/sites-enabled/default
Test Nginx configuration:
bashsudo nginx -t
Reload Nginx:
bashsudo systemctl reload nginx
Restart Nginx:
bashsudo systemctl restart nginx
Check Nginx status:
bashsudo systemctl status nginx
SSL Certificates
Official documentation: Certbot Instructions
Obtain SSL certificate:
For automatic Nginx configuration:
bashsudo certbot --nginx -d example.com -d www.example.com
Or use certonly mode (manual configuration):
bashsudo certbot certonly --nginx -d example.com -d www.example.com
Test auto-renewal:
Certbot installed via snap automatically sets up renewal. Test it:
bashsudo certbot renew --dry-run
Verify auto-renewal timer:
Check that the snap timer is active:
bashsudo snap list certbot sudo systemctl list-timers | grep certbot
The snap installation automatically handles renewal twice daily.
Check certificate expiration:
bashsudo certbot certificates
Renew certificates manually (if needed):
bashsudo certbot renew
Development Tools
Git
Install Git:
bashsudo apt install git
Configure Git:
bashgit config --global user.name "Your Name" git config --global user.email "your.email@example.com"
Setup GitHub SSH:
Generate SSH key:
bashssh-keygen -t ed25519 -C "your_email@example.com"
Start SSH agent:
basheval "$(ssh-agent -s)"
Create or edit SSH config:
bashnano ~/.ssh/config
Add:
bashHost github.com User git Hostname github.com PreferredAuthentications publickey IdentityFile ~/.ssh/id_ed25519
Add SSH key to agent:
bashssh-add ~/.ssh/id_ed25519
Display public key (add this to GitHub):
bashcat ~/.ssh/id_ed25519.pub
Test connection:
bashssh -T git@github.com
Node Version Manager
Official repository: nvm-sh/nvm
Install build dependencies:
bashsudo apt update sudo apt install build-essential libssl-dev
Install NVM:
bashcurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Or using wget:
bashwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Load NVM:
Close and reopen your terminal, or run:
bashexport 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:
bashnvm ls-remote
Install latest LTS version:
bashnvm install --lts nvm use --lts
Install specific version:
bashnvm install 20.10.0 nvm use 20.10.0
Set default Node version:
bashnvm alias default 20.10.0
List installed versions:
bashnvm ls
Alternative: Consider fnm for a faster, Rust-based Node version manager.
Docker
Official documentation: Docker Engine Install
Install Docker:
bashsudo 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:
bashecho \ "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:
bashsudo 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:
bashsudo usermod -aG docker $USER
Important: Log out and log back in for the group changes to take effect, or run:
bashnewgrp docker
Verify Docker installation:
bashdocker run hello-world
Enable Docker to start on boot:
bashsudo systemctl enable docker sudo systemctl enable containerd
Basic Docker commands:
List running containers:
bashdocker ps
List all containers:
bashdocker ps -a
View Docker images:
bashdocker images
Stop a container:
bashdocker stop CONTAINER_ID
Remove a container:
bashdocker rm CONTAINER_ID
Docker Compose:
Docker Compose is included with the installation above. Verify:
bashdocker compose version
Start services defined in docker-compose.yml:
bashdocker compose up -d
Stop services:
bashdocker compose down
Using Docker in cron jobs:
Since your user is in the docker group, you can run Docker commands in cron jobs:
bashcrontab -e
Example cron job:
bash0 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:
bashnpm install -g pm2
Start an application:
bashpm2 start app.js --name myapp
List applications:
bashpm2 list
Monitor applications:
bashpm2 monit
View logs:
bashpm2 logs pm2 logs myapp
Restart application:
bashpm2 restart myapp
Stop application:
bashpm2 stop myapp
Setup PM2 startup script:
bashpm2 startup systemd
Run the command that PM2 outputs, then:
bashpm2 save
Configuration file example:
Create ecosystem.config.js:
javascriptmodule.exports = { apps: [ { name: 'myapp', script: './app.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, }, ], };
Start with config:
bashpm2 start ecosystem.config.js
Monitoring & Utilities
Essential tools:
bashsudo 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:
bashdf -h ncdu /
Check memory usage:
bashfree -h
Check running processes:
bashhtop
View system logs:
bashsudo journalctl -xe sudo journalctl -u nginx
Check open ports:
bashsudo netstat -tulpn sudo ss -tulpn
Last updated