13 min read

How to Setup a Secure Ubuntu Home Server: A Complete Guide

Table of Contents

Setting up a home server can seem daunting, but with this blog post, you too can transform an old PC into a secure home server! This blog post will walk you through every step, from basic SSH setup to advanced security hardening.

Why Set Up a Home Server?

  • Host personal websites or blogs
  • Test and develop software projects
  • Store and share files across your home network
  • Practice system administration skills in a safe environment

Prerequisites

Before we begin, ensure you have:

  • A PC with Ubuntu installed (Either Ubuntu Server or Desktop is fine)
  • Basic command line familiarity
  • Administrative access to your home router
  • Another computer to test SSH connections

Step 1: Setting Up SSH Access

SSH (Secure Shell) is essential for any developer working with remote servers. Unlike older protocols like Telnet, SSH encrypts all communication between your local machine and the server, protecting credentials and code from network eavesdropping.

Install and Enable OpenSSH Server

On your server, run:

sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl status ssh

What this does: The OpenSSH server daemon listens for incoming SSH connections. The enable command ensures SSH starts automatically on boot, while status verifies the service is running correctly. You should see “active (running)” in the output.

Find Your Server’s IP Address

On your server, run:

hostname -I

This returns your server’s current IP address (something like 192.168.1.100). Write this down as you’ll use it throughout the guide for remote connections.

Test SSH Connection

From your client computer (laptop/desktop), try connecting:

ssh username@192.168.1.100

You’ll need to enter your password at this point, which establishes that you’re authorized to access the system.

Set Up Key-Based Authentication

Password authentication is vulnerable to brute-force attacks where attackers systematically try common passwords. SSH keys provide cryptographic authentication that’s virtually impossible to crack.

On your client computer (laptop/desktop), generate an SSH key pair:

ssh-keygen -t ed25519 -C "your-email@example.com"

Key type explanation: Ed25519 keys are smaller, faster, and more secure than traditional RSA keys. When prompted for a location, press Enter to use the default. You can optionally add a passphrase for additional security.

Still on your client computer, copy your public key to the server:

ssh-copy-id username@192.168.1.100

How key authentication works: Your private key stays on your client machine, while the public key is stored on the server. When connecting, your client uses the private key to prove it has the matching pair, like a cryptographic lock-and-key system.

Test from your client computer: You should now be able to SSH without entering a password:

ssh username@192.168.1.100

Step 2: Configuring a Static IP Address

Dynamic IP addresses can change when your router reboots or reassigns addresses, breaking your development workflow. Static IPs ensure your server remains accessible at a consistent address.

Understanding Netplan

Ubuntu uses Netplan for network configuration, which provides a simplified interface to underlying network management systems.

On your server, navigate to the netplan directory:

cd /etc/netplan/
ls

You should see a configuration file like 01-network-manager-all.yaml.

Create a Backup

Always backup before making network changes, as configuration errors can lock you out entirely:

sudo cp /etc/netplan/01-network-manager-all.yaml /etc/netplan/01-network-manager-all-BACKUP.yaml

Get Network Interface Information

Before creating the configuration, gather the information you’ll need:

ifconfig -a

Look for something like enp42s0 (ignore the lo interface). Modern network interfaces use predictable naming based on hardware location rather than the generic eth0.

Find your current gateway (router IP):

ip route | grep default

This typically shows something like 192.168.1.1 for home networks.

Create Static IP Configuration

Now create a new configuration file with your specific details:

sudo vim /etc/netplan/static.yaml

Use this template, replacing the values with your information:

network:
  version: 2
  renderer: networkd
  ethernets:
    YOUR_INTERFACE_NAME:  # Replace with actual interface (e.g., enp42s0)
      addresses:
        - 192.168.1.100/24  # Your chosen static IP
      routes:
        - to: default
          via: 192.168.1.1   # Your router's gateway IP
      nameservers:
          addresses: [1.1.1.1, 1.0.0.1]  # Cloudflare DNS

Configuration breakdown:

  • YOUR_INTERFACE_NAME: Replace with your actual interface name from ifconfig output
  • addresses: - 192.168.1.100/24 sets your static IP with a /24 subnet mask (allowing 254 addresses)
  • routes: - to: default via: 192.168.1.1 directs all internet traffic through your router
  • nameservers specifies DNS servers for domain name resolution (Cloudflare’s fast, privacy-focused servers)

Apply the Configuration

Set proper permissions and apply the new network configuration:

sudo chmod 600 /etc/netplan/static.yaml
sudo netplan apply
sudo reboot

Why reboot: Ensures all network services restart with the new configuration.

Test from your client computer:

ssh username@192.168.1.100

Replace the IP with whatever static IP you configured.

Step 3: Hardening with UFW Firewall

A firewall acts as a network security guard, controlling which connections are allowed to reach your server. This is critical for developers as many development tools and services can inadvertently expose security vulnerabilities.

Install and Enable UFW

On your server:

sudo apt install ufw
sudo systemctl status ufw
sudo ufw enable

What UFW provides: UFW (Uncomplicated Firewall) simplifies iptables management, making it accessible for developers who need basic firewall protection without learning complex iptables syntax.

Set Default Policies

Implement a default-deny security posture:

sudo ufw default deny incoming
sudo ufw default deny outgoing

Security principle: This “default deny” approach blocks all connections by default, then explicitly allows only what you need. This minimizes your attack surface and prevents forgotten services from being exploited.

Allow Essential Services

Open only the ports your development workflow requires:

# Web traffic (for package updates and browsing)
sudo ufw allow 80,443/tcp
sudo ufw allow out 80,443/tcp

# SSH access
sudo ufw allow 22/tcp
sudo ufw allow out 22/tcp

# NTP for time synchronization
sudo ufw allow 123/udp
sudo ufw allow out 123/udp

# DNS resolution
sudo ufw allow in from any to any port 53
sudo ufw allow out from any to any port 53

Port explanations:

  • 80/443: HTTP and HTTPS for web traffic, essential for package managers and development tools
  • 22: SSH for remote access (we’ll change this port later for security)
  • 123: Network Time Protocol ensures accurate timestamps for certificates and logging
  • 53: DNS resolution allows your server to translate domain names to IP addresses

Check Firewall Status

sudo ufw status numbered

You should see comprehensive rules for both IPv4 and IPv6 traffic, with numbered entries for easy management.

Step 4: Network Time Protocol (NTP) Setup

Accurate time synchronization is crucial for developers working with SSL certificates, authentication tokens, and distributed systems where timing matters.

Backup Current Configuration

Always preserve the original configuration:

sudo cp --archive /etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf-COPY-$(date +"%Y%m%d%H%M%S")

Backup strategy: The --archive flag preserves all file attributes, while the timestamp ensures unique backup names.

Configure NTP

Edit the time synchronization configuration:

sudo vim /etc/systemd/timesyncd.conf

Add these lines:

[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org
FallbackNTP=ntp.ubuntu.com

NTP server selection: The pool.ntp.org project provides a worldwide cluster of time servers with automatic load balancing. Multiple servers ensure redundancy if one becomes unavailable.

Restart and Verify

sudo systemctl restart systemd-timesyncd
sudo systemctl status systemd-timesyncd

Verification importance: Always confirm services restart successfully after configuration changes. Check for any error messages in the status output.

Set Your Timezone

Check and set the correct timezone for your location:

timedatectl | grep Time
sudo timedatectl set-timezone America/Vancouver

Developer consideration: Use your local timezone for development servers to ensure log timestamps align with your working hours, making debugging easier.

Step 5: Advanced SSH Security

Default SSH configurations prioritize compatibility over security. For development servers, we can implement stronger security measures while maintaining usability.

Change SSH Port

Moving SSH off the default port eliminates most automated attacks that scan for port 22:

sudo ufw allow 2222/tcp
sudo ufw allow out 2222/tcp

Security through obscurity: While not a substitute for real security, non-standard ports make your server invisible to automated scanners that target default configurations.

Backup SSH Configuration

Preserve the original configuration before making changes:

sudo cp --preserve /etc/ssh/sshd_config /etc/ssh/sshd_config.$(date +"%Y%m%d%H%M%S")

Apply Hardened SSH Configuration

Edit the SSH daemon configuration:

sudo vim /etc/ssh/sshd_config

Replace the entire contents with this hardened configuration:

# Host key algorithms by preference
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

# Modern cryptographic algorithms
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com

# Security settings
Port                            2222
LogLevel                        VERBOSE
Protocol                        2
PermitUserEnvironment           no
PermitRootLogin                 no
PubkeyAuthentication            yes
PasswordAuthentication          no
PermitEmptyPasswords            no
MaxAuthTries                    3
MaxSessions                     2
X11Forwarding                   no
IgnoreRhosts                    yes
UseDNS                          no
ClientAliveCountMax             0
ClientAliveInterval             300
AllowUsers                      YOUR_USERNAME

# Disable forwarding features
AllowAgentForwarding            no
AllowTcpForwarding              no
AllowStreamLocalForwarding      no
GatewayPorts                    no
PermitTunnel                    no

# SFTP subsystem
Subsystem sftp  /usr/lib/openssh/sftp-server

# Additional hardening
Compression                     no
PrintMotd                       no
TCPKeepAlive                    no
ChallengeResponseAuthentication no
UsePAM                          yes
AcceptEnv LANG LC_*

Critical security settings explained:

Cryptographic algorithms: We prioritize modern algorithms like Ed25519 and ChaCha20-Poly1305 that provide better security and performance than older alternatives.

Access control:

  • PermitRootLogin no: Prevents direct root access, forcing attackers to compromise a user account first
  • PasswordAuthentication no: Enforces key-based authentication only
  • MaxAuthTries 3: Limits brute-force attempts
  • AllowUsers YOUR_USERNAME: Whitelist approach - replace with your actual username

Attack prevention:

  • X11Forwarding no: Prevents GUI forwarding that could be exploited
  • AllowTcpForwarding no: Blocks using SSH as a tunnel for other protocols
  • ClientAliveInterval 300: Automatically disconnects idle sessions after 5 minutes

Important: Replace YOUR_USERNAME with your actual username before saving!

Restart SSH and Verify

Apply the new configuration:

sudo systemctl restart sshd
sudo ss -tlpn | grep ssh

You should see SSH listening on port 2222.

Test New SSH Configuration

From your client computer, test the new port (keep your current session open):

ssh -p 2222 username@192.168.1.100

Once confirmed working, remove the old SSH port from the firewall:

sudo ufw delete allow 22/tcp
sudo ufw delete allow out 22/tcp

Optional: Auto-logout After Inactivity

Add automatic logout for enhanced security:

echo 'TMOUT=300' >> ~/.bashrc

This logs out idle sessions after 5 minutes, preventing unauthorized access if you forget to disconnect.

Strengthen SSH Keys

Remove weak Diffie-Hellman moduli:

sudo cp --archive /etc/ssh/moduli /etc/ssh/moduli-COPY-$(date +"%Y%m%d%H%M%S")
sudo awk '$5 >= 3071' /etc/ssh/moduli | sudo tee /etc/ssh/moduli.tmp
sudo mv /etc/ssh/moduli.tmp /etc/ssh/moduli

What this does: Removes cryptographic parameters weaker than 3072 bits, preventing attacks against weak Diffie-Hellman key exchange.

Create a warning banner for legal protection:

sudo vim /etc/issue.net

Add this content:

This system is for the use of authorised users only.

Individuals using this computer system without authority, or in excess of their authority, are subject to having all of their activities on this system monitored and recorded by system personnel.

In the course of monitoring individuals improperly using this system, or in the course of system maintenance, the activities of authorised users may also be monitored.

Anyone using this system expressly consents to such monitoring and is advised that if such monitoring reveals possible evidence of criminal activity, system personnel may provide the evidence of such monitoring to law enforcement officials.

Legal importance: Warning banners establish your authority to monitor system usage and can provide legal protection in case of unauthorized access attempts.

Step 6: Git Configuration for Development

Development servers often need Git integration for code deployment and version control workflows.

Allow Git Port (Optional)

If you need the Git protocol (typically not recommended for security reasons):

sudo ufw allow 9418/tcp

Security note: The Git protocol (port 9418) is unencrypted. Prefer HTTPS or SSH for repository access when possible.

Configure Git for SSH

For firewall-friendly Git operations with GitHub:

# Standard Git SSH URL
git remote add origin git@github.com:username/repository.git

# Firewall-friendly alternative using port 443
git remote add origin ssh://git@ssh.github.com:443/username/repository.git

Why port 443: GitHub provides SSH access on the HTTPS port (443) to bypass firewalls that block the standard SSH port (22). This ensures Git operations work even with restrictive firewall rules.

Development Port Considerations

If hosting development applications, you might need additional ports:

# Common development server ports
sudo ufw allow 3000/tcp  # React development server
sudo ufw allow 8080/tcp  # Alternative HTTP port
sudo ufw allow 5000/tcp  # Flask default port
sudo ufw allow 8000/tcp  # Django development server

Security practice: Only open ports when actively needed, and close them when development work is complete.

Additional Security Considerations

Network-Level Restrictions

For maximum security, restrict SSH access to your local network:

sudo ufw allow from 192.168.1.0/24 to any port 2222

Network segmentation: This allows SSH connections only from your local subnet (192.168.1.x), completely blocking internet-based attacks.

Regular Maintenance Tasks

Weekly maintenance:

# Update package lists and check for updates
sudo apt update && sudo apt upgrade

# Monitor SSH access attempts
sudo journalctl -u ssh

# Review firewall status
sudo ufw status verbose

# Check for failed login attempts
sudo grep "Failed password" /var/log/auth.log

Monthly security review:

# Audit listening services
sudo netstat -tuln

# Review active user sessions
who

# Check system resource usage
df -h && free -m

Development-Specific Monitoring

Create useful aliases for server management:

# Add to ~/.bashrc
alias sshlog='sudo tail -f /var/log/auth.log | grep ssh'
alias ports='sudo netstat -tuln'
alias connections='sudo ss -tuln'
alias gitlog='git log --oneline -10'

Log analysis for developers: Understanding SSH logs helps identify both security issues and connection problems that could affect your development workflow.

Conclusion

You now have a comprehensively secured Ubuntu development server with:

  • ✅ Encrypted SSH access with Ed25519 key authentication
  • ✅ Static IP configuration for consistent connectivity
  • ✅ Hardened firewall rules implementing default-deny policies
  • ✅ Network time synchronization for accurate timestamps
  • ✅ Secure SSH configuration on port 2222 with modern cryptography
  • ✅ Git-ready environment with firewall-friendly repository access
  • ✅ Legal protection through warning banners
  • ✅ Monitoring capabilities for ongoing security assessment