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 fromifconfig
outputaddresses: - 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 routernameservers
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 firstPasswordAuthentication no
: Enforces key-based authentication onlyMaxAuthTries 3
: Limits brute-force attemptsAllowUsers YOUR_USERNAME
: Whitelist approach - replace with your actual username
Attack prevention:
X11Forwarding no
: Prevents GUI forwarding that could be exploitedAllowTcpForwarding no
: Blocks using SSH as a tunnel for other protocolsClientAliveInterval 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.
Add Legal Banner
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