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