Set Up a Secure Webserver on Linode: Step-by-Step Deployment Guide

Prerequisites #

Make sure you have completed the guide Set Up a Linode Compute Instance with Best Practices. This guide builds on that setup to configure a secure web server.

Step 1: Harden SSH #

Read: workflow.

Step 2: Install UFW #

Install:

bash
sudo apt install ufw -y

Start:

bash
sudo ufw enable

Auto start at boot:

bash
sudo systemctl enable ufw

Verify installation:

bash
sudo ufw status

Step 3: Configure UFW #

Set default rules:

Pro tip: Deny all traffic by default to create a secure baseline. Then allow only what is needed.

bash
sudo ufw default deny incoming
sudo ufw default deny outgoing

Allow services:

Always specify the protocol (tcp or udp) unless you have a reason to allow both.

bash
sudo ufw allow 80/tcp              # Allow incoming HTTP traffic
sudo ufw allow 443/tcp             # Allow incoming HTTPS traffic
sudo ufw allow out 80/tcp          # Allow outgoing HTTP traffic
sudo ufw allow out 443/tcp         # Allow outgoing HTTPS traffic
sudo ufw limit <custom_port>/tcp   # SSH (Limit connections to prevent brute-force attacks)

Enable UFW:

bash
sudo ufw enable

Verify:

bash
sudo ufw status

Output:

  • Status: active
  • Default: deny (incoming), deny (outgoing), disabled (routed)
To Action From
80/tcp ALLOW Anywhere
80/tcp ALLOW OUT Anywhere
80/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW OUT Anywhere (v6)
443/tcp ALLOW Anywhere
443/tcp ALLOW OUT Anywhere
443/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW OUT Anywhere (v6)
*****/tcp LIMIT Anywhere
*****/tcp (v6) LIMIT Anywhere (v6)

Step 4 : Set Up Automatic OS Updates #

Install

bash
sudo apt install unattended-upgrades -y

Enable:

Security updates for both OS and installed packages are enabled by default.

bash
sudo dpkg-reconfigure unattended-upgrades

During setup, you’ll be prompted to allow automatic updates.

  • Select: Yes
  • This enables unattended upgrades, automatically creating the required configuration files and scheduling security updates.

Configure

Create backup:

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

Open:

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

Modify:

Remove the // characters to enable a setting.

/etc/apt/apt.conf.d/50unattended-upgrades
# Include security and regular updates only
Unattended-Upgrade::Allowed-Origins {
  "${distro_id}:${distro_codename}"; 
  "${distro_id}:${distro_codename}-security";
  ...
};

# Automatic removal of unused dependencies
Unattended-Upgrade::Remove-Unused-Dependencies "true";

# Automatic reboot at 4:00 AM
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

# Send email notifications (optional) 
Unattended-Upgrade::Mail "[email protected]";

Create backup:

bash
sudo cp /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/20auto-upgrades.bak

Open:

bash
sudo vim /etc/apt/apt.conf.d/20auto-upgrades

Modify:

/etc/apt/apt.conf.d/20auto-upgrades
# Check for updates every day
APT::Periodic::Update-Package-Lists "1";

# Download available updates every day
APT::Periodic::Download-Upgradeable-Packages "1";

# Install security updates every day
APT::Periodic::Unattended-Upgrade "1";

# Remove unused packages after upgrading every 7 days
APT::Periodic::AutocleanInterval "7";

Reload config:

Required after making changes.

bash
sudo systemctl restart unattended-upgrades

Test configuration:

bash
sudo unattended-upgrades --dry-run --debug

Simulates an unattended upgrade run and prints debug output, allowing you to verify:

  • Which packages would be upgraded

  • If your configuration is working correctly

  • Whether updates are blocked due to policy, pinning, or origin filtering

Expected output (example):

text
Starting unattended upgrades script
Allowed origins are: ...
...
No packages found that can be upgraded unattended and no pending auto-removals