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:
sudo apt install ufw -y
Start:
sudo ufw enable
Auto start at boot:
sudo systemctl enable ufw
Verify installation:
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.
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.
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:
sudo ufw enable
Verify:
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
sudo apt install unattended-upgrades -y
Enable:
Security updates for both OS and installed packages are enabled by default.
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:
sudo cp /etc/apt/apt.conf.d/50unattended-upgrades /etc/apt/apt.conf.d/50unattended-upgrades.bak
Open:
sudo vim /etc/apt/apt.conf.d/50unattended-upgrades
Modify:
Remove the //
characters to enable a setting.
# 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:
sudo cp /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/20auto-upgrades.bak
Open:
sudo vim /etc/apt/apt.conf.d/20auto-upgrades
Modify:
# 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.
sudo systemctl restart unattended-upgrades
Test configuration:
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):
Starting unattended upgrades script
Allowed origins are: ...
...
No packages found that can be upgraded unattended and no pending auto-removals