Table of Contents
This repository documents my journey of building and configuring a DIY home server.
I decided to create this server because I had a couple of old laptops that were just sitting unused. Instead of letting them collect dust, I repurposed them into a small home server environment.
This repo contains the commands, configurations, and notes from the setup process.
Later I plan to add explanations, screenshots, and short videos.
Currently using two machines as part of this DIY server setup. The system is built around a primary server and a secondary backup server.
- CPU: Intel i5-3320M
- RAM: 8 GB DDR3
- Storage: 500 GB SATA III SSD
- 1 Gbps NIC with support for Wake on Lan
This machine acts as the main server, as it has better performance and significantly more storage available. It hosts the primary services of the system and runs most applications through Docker containers.
- CPU: AMD A8-7100
- RAM: 8 GB DDR3
- Storage: 120 GB SATA III SSD
- 1 Gbps NIC with support for Wake on Lan
This machine acts as a backup server. The HP ProBook struggles with performance and has limited storage, so it is mainly used for storing backups of important data.
The server currently runs:
- Debian 13 Stable (Trixie)
My initial plan was to run Proxmox and structure the system with:
- 1 Debian VM running Docker conatiners
- several LXC containers for different services
The hardware was not suitable for a full virtualization stack. Running Proxmox, a VM, and multiple containers would have added too much overhead on machine with limited CPU and RAM.
Instead, I installed Debian 13 Stable directly on the ThinkPad T430 (bare metal).
The system was installed using a minimal setup with no desktop environment, since:
- the server is managed entirely through SSH
- a graphical interface is not necessary
- avoiding a GUI saves RAM and CPU resources
In most home networks, devices receive their local IP address dynamically through DHCP.
This means the router automatically assigns an available IP address to each device when it connects to the network.
In order to access the server or any of its services, you need to know its local IP address.
This can be retrieved either from the server itself or from the router’s admin panel, which can be annoying.
I chose to create a DHCP reservation from the router, which always assigns the same IP address to each server based on its MAC address.
Note: It is also possible to set a static IP manually on the server itself, but using the router simplifies management.
Router configuration example:
In my setup, the router is configured to always assign the same IP address for each server.
For now, I only have a single Wireless Router handling all roles.
Note: In the future, I plan to separate these functions into specialized devices:
- Gigabit switch for wired connections
- Custom router running OPNsense or pfSense
- Access Point for WiFi Router and server setup:
Below is an ASCII diagram illustrating the layout of the whole network:
ISP
│
WAN
┌─────────────────────────────────┐
│ Wireless Router │
│---------------------------------│
│ WAN │
│ LAN0 │
│ LAN1 │
│ LAN2 │
│ WiFi │
└─────────────────────────────────┘
LAN0 LAN1 LAN2 WiFi
│ │ │ │
│ │ │ ├─device0
│ │ │ ├─device1
│ │ │ ├─device2
│ │ │ ├─device3
│ │ │ └─deviceN
│ │ │
│ │ │
│ │ └── My Laptop
│ │
│ └── HP Server
│
└── ThinkPad Server
Since the server runs on a laptop (ThinkPad T430) instead of traditional server hardware, a few adjustments are necessary.
Laptops are designed to prioritize power saving and efficiency, which can interfere with a system intended to run continuously as a server.
The following configurations ensure the laptop stays powered on even when the lid is closed, prevents sleep/suspend states, allows remote power-on, and helps preserve battery health.
Prevent the laptop from sleeping when the lid is closed.
Edit:
/etc/systemd/logind.conf
Uncomment and modify the following parameters:
Restart the service:
sudo systemctl restart systemd-logindDisable all sleep targets to ensure the server remains active.
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.targetLimit battery charging to 80% to reduce long-term battery wear.
echo 80 | sudo tee /sys/class/power_supply/BAT0/charge_control_end_thresholdNote: Battery device names (for example
BAT0) and configuration paths may differ depending on the laptop model and hardware configuration.
Note: Some laptops may not support limiting battery charge at the hardware/firmware level, meaning this feature might not be available on all systems.
Note: The link below includes additional methods and troubleshooting steps if this method does not work on a specific system.
Reference:
https://ubuntuhandbook.org/index.php/2024/02/limit-battery-charge-ubuntu/
Wake-on-LAN allows the server to be powered on remotely by sending a magic packet to its network interface.
Important: Wake-on-LAN must first be enabled in the system BIOS/UEFI.
Before configuring WoL, identify the network interface name and MAC address:
ip aThis command lists all network interfaces. From here you can find:
- the network interface name (for example
enp1s0) - the MAC address used for Wake-on-LAN
The MAC address can also usually be found in the router's admin panel under the list of connected devices.
Install the required tool:
sudo apt install ethtoolEnable WoL on the network interface:
sudo ethtool -s enp1s0 wol gTo make this persistent after reboot, create a systemd service:
sudo nano /etc/systemd/system/wol-enable.serviceService configuration:
[Unit]
Description=Configure Wake-up on LAN
After=network-online.target
[Service]
Type=oneshot
ExecStart=/sbin/ethtool -s enp1s0 wol g
[Install]
WantedBy=basic.target
Enable the service:
sudo systemctl enable wol-enable.serviceThe server can then be powered on remotely using:
wakeonlan <MAC_ADDRESS>1. Client-Side Demonstration
In this demo, two terminals are used. The first terminal sends the wakeonlan magic packet, while the second terminal constantly pings 192.168.0.108 on port 22 to monitor exactly when the server wakes up and becomes available.
wakeonlanClient.mp4
2. Server-Side Demonstration This video shows the server, waking up. You can see the server powering on and spinning up immediately after receiving the magic packet over the network.
wakeonlanServer.mp4
Note: There are multiple ways to configure Wake-on-LAN depending on the system, network interface, and distribution. The reference below includes alternative approaches and troubleshooting information.
Reference:
https://www.thelinuxvault.net/blog/how-to-wake-on-lan-supported-host-over-the-network-using-linux/
The following configurations are applied to both servers.
They provide:
- Remote management (SSH)
- Basic system monitoring tools
SSH allows the server to be managed remotely from another machine through the terminal.
Install the SSH server:
sudo apt install openssh-serverEnable and start the service:
sudo systemctl enable ssh
sudo systemctl start sshFrom another machine on the network, connect using the following command.
In my case:
ssh dev@192.168.0.109Where:
devis the username on the server192.168.0.109is the server's local IP address
For Linux and Windows, I usually connect directly from the terminal using the command shown above.
For Android, I use the Termius SSH client to connect to the servers.
![]() |
![]() |
Some lightweight tools are installed to quickly inspect system information and resource usage.
Displays system information.
sudo apt install fastfetchRun:
fastfetchInteractive resource monitor.
sudo apt install btopRun:
btopAlternative terminal system monitor.
sudo apt install htopRun:
htopThe ThinkPad T430 acts as the main server in this setup.
It is responsible for:
- running Docker services
- providing network storage (NAS) via Samba
- storing application data and media
Before setting everything up, I defined a directory structure to clearly separate:
- Docker configurations
- shared data (NAS)
- service-specific data
Planned layout:
/
├── srv
│ └── docker
│ └── navidrome
│ └── docker-compose.yml
│
└── data
├── shares
│ └── media
│ └── music
│
└── services
└── navidrome
├── navidrome.db
└── cache
This structure keeps:
- configs in
/srv/docker - user-accessible files in
/data/shares - service data isolated in
/data/services
To keep permissions clean and manageable, I created a dedicated group for storage access and configured a shared directory.
sudo groupadd storage
sudo usermod -aG storage dev
sudo chown -R root:storage /data
sudo chmod -R 2775 /dataExplanation:
storage→ shared group for file accessdev→ added to the group/data→ main shared directory2775→ ensures new files inherit the group
Samba is used to expose the /data directory to other devices on the network.
I chose Samba (SMB) instead of NFS because I access the server from multiple operating systems:
- Linux
- Windows
- Android
SMB is natively supported on all of these platforms, making it a more flexible choice.
NFS is more common in Linux-only environments, but requires additional setup or third-party tools on Windows and Android.
sudo apt update
sudo apt install sambaEnable and start the service:
sudo systemctl enable smbd
sudo systemctl start smbdCheck status:
sudo systemctl status smbdEnable Samba access for the user:
sudo smbpasswd -e devEdit the Samba configuration file:
sudo nano /etc/samba/smb.confAdd the following:
[global]
server string = DebiServ
workgroup = WORKGROUP
security = user
map to guest = Bad User
[data]
path = /data
force user = dev
force group = storage
create mask = 0664
force create mode = 0664
directory mask = 0775
force directory mode = 0775
browseable = yes
writable = yes
guest ok = no
read only = no
[global]→ general server settingsserver string→ server name shown on the networkworkgroup→ Windows workgroup (default: WORKGROUP)security = user→ requires authenticationmap to guest = Bad User→ unknown users are treated as guests
[data]→ shared folder definitionpath = /data→ directory being sharedforce user / group→ ensures consistent ownership of filescreate mask = 0664→ file permissions (rw-rw-r--)directory mask = 0775→ directory permissions (rwxrwxr-x)browseable = yes→ visible on the networkwritable = yes→ allows writingguest ok = no→ disables anonymous accessread only = no→ allows modifications
Note: I recommend creating the Samba configuration from scratch instead of modifying the default file.
This helps keep the configuration clean and avoids unnecessary or confusing defaults.You can keep the original file as a reference by renaming it:
sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.oldThen create a new configuration file:
sudo nano /etc/samba/smb.conf
Apply changes:
sudo systemctl restart smbdAndroid
When connected to the WireGuard tunnel, the Samba share can also be accessed securely using the server's VPN IP address.
Navidrome is a lightweight self-hosted music server and web-based player.
It allows streaming your personal music library from:
- web browser
- Android apps
- Subsonic-compatible clients
- other devices on the network
Official documentation:
https://www.navidrome.org/docs/installation/docker/
My personal music collection is stored locally and served through Navidrome.
Library details:
- around 1100 songs
- approximately 35 GB
- all files in FLAC format
- organized into folders representing playlists
I originally kept most of my music on Spotify.
Later, I transferred the library to Deezer, then used Deemix to download and preserve the collection locally in FLAC quality.
This gives me:
- full ownership of my library
- offline access
- no subscription dependency
- higher audio quality
- compatibility with self-hosted streaming
Docker project files are stored in:
/srv/docker/navidromeCreate:
sudo nano /srv/docker/navidrome/docker-compose.ymlAdd:
services:
navidrome:
image: deluan/navidrome:latest
user: 1000:1000
ports:
- "4533:4533"
restart: unless-stopped
environment:
ND_LOGLEVEL: info
volumes:
- "/data/services/navidrome/:/data"
- "/data/shares/media/music/:/music:ro"/data/services/navidrome/Used for:
- application data
- database
- cache
- metadata
This folder is also accessible through Samba for easier management.
/data/shares/media/music/Used as the main music library location.
This is where I store my music files.
It is mounted as:
/music:roMeaning:
- read-only for the container
- protects music files from accidental modification
From the project directory:
cd /srv/docker/navidrome
docker compose up -dOpen in browser:
http://SERVER_IP:4533
Example:
http://192.168.0.108:4533
I currently use the following clients to access Navidrome.
- Web browser
- SubTUI (Subsonic-compatible CLI client)
- Symfonium (paid one-time purchase, around 25 RON)
Note: There are multiple options for clients compatible with Navidrome: https://www.navidrome.org/apps/
Navidrome can also be accessed remotely through the WireGuard VPN tunnel.
This allows me to stream my music library securely from outside my home network without exposing the service publicly to the internet.
Accessing Navidrome from browser through the VPN tunnel:
Accessing Navidrome remotely through Symfonium on Android:
This keeps the service fully private while still allowing remote streaming from anywhere.
My music library is organized in folders, where each folder represents a playlist.
Navidrome recognizes standard playlist files such as .m3u, so I created a small script to automatically generate playlist files from those folders.
Create:
nano importInNavidrome.shAdd:
#!/bin/bash
cd /data/shares/media/music/ || exit 1
for dir in */; do
find "$dir" -type f -name "*.flac" | sort > "${dir%/}.m3u"
doneMake executable:
chmod +x importInNavidrome.shRun:
./importInNavidrome.shExample structure before running:
/music/playlists/
├── Good Shit!/
├── Pop/
└── Killing the Classics/
After running the script:
/music/playlists/
├── Good Shit!/
├── Good Shit!.m3u
├── Pop/
├── Pop.m3u
├── Killing the Classics/
└── Killing the Classics.m3u
Each generated .m3u file contains the .flac tracks found inside the matching folder.
This allows Navidrome to import folder-based playlists automatically.
For note-taking and knowledge management, I use Obsidian together with Syncthing.
Obsidian is essentially my self-hosted equivalent of Notion, but focused on local Markdown files and vaults.
Syncthing handles synchronization across all devices.
This setup gives me two possible workflows:
- Server-hosted only → run Obsidian in Docker and access it through the browser
- Fully synchronized setup → use Syncthing so every device can use the native Obsidian app while keeping everything synced
The second option is what I mainly use.
Official documentation:
https://docs.linuxserver.io/images/docker-obsidian/
Docker Compose path:
/srv/docker/obsidian/docker-compose.yml
Docker Compose:
services:
obsidian:
image: lscr.io/linuxserver/obsidian:latest
container_name: obsidian
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Bucharest
volumes:
- /data/shares/media/notes:/config
ports:
- 3000:3000
- 3001:3001
shm_size: "1gb"
restart: unless-stoppedVault storage:
/data/shares/media/notes
This directory stores all vaults and note data.
Start the container:
cd /srv/docker/obsidian
docker compose up -dAccess in browser:
https://192.168.0.108:3001
Initial setup:
- create
vaults/ - create first vault (
schoolin my case)
Screenshot:
Official documentation:
https://docs.linuxserver.io/images/docker-syncthing/
Docker Compose path:
/srv/docker/syncthing/docker-compose.yml
Docker Compose:
services:
syncthing:
image: lscr.io/linuxserver/syncthing:latest
container_name: syncthing
hostname: syncthing-server
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Bucharest
volumes:
- /data/services/syncthing:/config
- /data/shares/media/notes/vaults:/obsidian
ports:
- 8384:8384
- 22000:22000/tcp
- 22000:22000/udp
- 21027:21027/udp
restart: unless-stoppedConfig storage:
/data/services/syncthing
Shared notes directory:
/data/shares/media/notes/vaults
Start the container:
cd /srv/docker/syncthing
docker compose up -dAccess dashboard:
http://192.168.0.108:8384
Create a Syncthing user:
On PC / Phone:
Install Syncthing and open the dashboard.
Create a user on the device.
On the server:
Show the device ID (string or QR code).
Copy it.
Screenshot:
On the client:
Paste the server ID and add it.
Screenshot:
Back on the server:
Accept the new device connection.
Screenshot:
On the server, create a shared folder inside Syncthing.
Folder path:
/obsidian
This corresponds to:
/data/shares/media/notes/vaults
because of the Docker volume mapping.
Screenshot:
Share it with the client:
On the client:
Accept the sync request.
Select where the vault should be stored.
In my case:
/home/onyo/Obsidian/
Screenshot:
The same exact process applies for mobile devices.
After synchronization is complete:
- the server keeps the main vault copy
- all devices stay synchronized automatically
- devices can use the native Obsidian app
- the browser-hosted Obsidian remains available as a fallback
This makes the setup flexible and fully self-hosted.
Both services can also be accessed remotely through the WireGuard tunnel.
Obsidian over VPN:
Syncthing GUI over VPN:
This allows secure remote note access without exposing anything publicly.
Live synchronization demonstration:
demoObidianSyncthing.mp4
Demo setup:
Left side:
- server-hosted Obsidian in browser
- server terminal monitoring file changes:
watch -n 1 -d ls -lh /data/shares/media/notes/vaults/schoolRight side:
- client native Obsidian app
- Dolphin file manager:
/home/onyo/Obsidian
The demo shows:
- editing on client → sync to server
- editing on server → sync to client
This verifies real-time synchronization across all devices while remaining fully self-hosted.
Lancache is a self-hosted caching solution designed to locally cache game downloads and updates.
It supports platforms such as:
- Steam
- Epic Games
- Riot Games
- Battle.net
- many others
Official website:
Lancache stores downloaded game files locally on the server.
Benefits:
- faster re-downloads
- reduced repeated internet bandwidth usage
- useful for multiple PCs on the same network
- ideal for LAN environments
If one machine downloads a game, the cached data can later be reused by other machines.
Clone the official repository:
cd /srv/docker
git clone https://github.com/lancachenet/docker-compose lancache
cd lancacheEdit the environment file:
sudo nano .envConfiguration example:
Start the service:
sudo docker compose up -dI only tested Lancache on my Linux laptop.
This is the method I used.
Edit the systemd-resolved configuration:
sudo nano /etc/systemd/resolved.confExample configuration:
Set:
DNS=192.168.0.108
FallbackDNS=1.1.1.1 8.8.8.8Where:
192.168.0.108→ my Lancache / home server1.1.1.1and8.8.8.8→ backup DNS resolvers
Restart the resolver service:
sudo systemctl restart systemd-resolvedFlush DNS cache:
sudo systemd-resolve --flush-cachesThis ensures new DNS requests use the updated configuration.
Run:
nslookup steam.cache.lancache.netExpected result:
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
Name: steam.cache.lancache.net
Address: 192.168.0.108
Where:
192.168.0.108= Lancache server IP
This confirms the domain resolves to the local cache server.
The game is downloaded normally from the internet while simultaneously being cached on the server.
After the first run, files are stored locally on the server.
Re-downloading the same game pulls files directly from the local server.
At my current location, the speed difference is not dramatic because I already have strong internet connectivity.
Current setup:
- 1 Gbit internet plan
- router Wi-Fi limited to around 500 Mbit/s
- laptop already maxing Wi-Fi throughput
So while Lancache is still noticeable, the gain is smaller.
I tested Lancache previously in another location with:
- 1 Gbit internet plan
- better router (Wi-Fi ~1 Gbit/s)
- real speeds around 800–900 Mbit/s
In that setup, the performance difference was much more visible.
Peak download speed was around 48 MB/s.
Once the files were cached locally, peak speed increased to around 74 MB/s.
Note: This service will likely not remain permanently on my server.
My current biggest bottleneck is storage capacity, and Lancache can consume a large amount of disk space over time.
I mainly deployed it as an experiment to test how it works.
I also host a Minecraft server on the home server.
It can be accessed both:
- through the local network (LAN)
- through the WireGuard VPN tunnel
However, in my current setup the VPN traffic is routed through a VPS relay, which adds extra latency.
Because Minecraft is sensitive to ping and responsiveness, the experience is noticeably better on the local network.
For that reason, I mainly use it as a LAN server, while VPN access remains useful for administration or occasional remote play.
For managing the server, I chose LinuxGSM.
Official documentation:
LinuxGSM is an excellent game server manager because it supports many games and automates tasks such as:
- downloading server files
- installing dependencies
- starting / stopping servers
- monitoring
- backups
- updates
It works largely out of the box without manually installing game server files and dependencies.
I chose it because I already used it successfully in the past on a VPS for a public Minecraft server.
I did not use the Docker version of LinuxGSM because:
- it is marked as experimental
- receives fewer updates
- native installation is simpler and more mature
There are several other great solutions:
- Pterodactyl Panel
- AMP (paid)
- Crafty Controller (Minecraft-focused)
More options:
https://www.ghostcap.com/game-server-control-panels
For my needs, LinuxGSM was the best fit.
Create a dedicated user:
sudo adduser mcserverOptional:
sudo usermod -aG sudo mcserverIf the user has sudo privileges, LinuxGSM can automatically install missing dependencies.
Switch user:
su - mcserverInstall LinuxGSM:
curl -Lo linuxgsm.sh https://linuxgsm.sh && chmod +x linuxgsm.sh && bash linuxgsm.sh mcserverInstall the Minecraft server:
./mcserver installRun these commands as the mcserver user:
./mcserver start
./mcserver stop
./mcserver restart
./mcserver details
./mcserver monitor
./mcserver update
./mcserver backup
./mcserver consoleExplanation:
start→ starts the Minecraft serverstop→ safely stops the serverrestart→ restarts the serverdetails→ shows server information (IP, ports, status, etc.)monitor→ checks if the server is running and can restart it if neededupdate→ updates server filesbackup→ creates a backupconsole→ opens the live server console
Example usage:
su - mcserver
./mcserver detailsInitially, I considered using symbolic links so the live server files would exist in:
/data/shares/minecraft
with symlinks inside:
/home/mcserver/
But using the built-in LinuxGSM backup system is much cleaner.
LinuxGSM backup command:
./mcserver backupBackup destination:
/data/shares/minecraft
Set permissions:
sudo chown -R mcserver:mcserver /data/shares/minecraft
sudo chmod -R 755 /data/shares/minecraftEdit config:
nano /home/mcserver/lgsm/config-lgsm/mcserver/mcserver.cfgSet:
backupdir="/data/shares/minecraft"
maxbackups="3"As user mcserver:
crontab -eAdd:
0 2 * * * /home/mcserver/mcserver backup > /dev/null 2>&1Meaning:
- Minute
0 - Hour
2 - Every day
So a backup runs daily at 02:00 AM.
Typical settings I modify:
difficulty=easy (Values: peaceful, easy, normal, hard)
pvp=true (Values: true, false)
gamemode=survival (Values: survival, creative, adventure, spectator)
force-gamemode=false (Values: true, false)
max-players=20 (Values: Any number)
online-mode=true (Values: true, false)
view-distance=10 (Values: Usually 5 to 32)
simulation-distance=10 (Values: Usually 5 to 32)Reference:
https://minecraft.wiki/w/Server.properties
Usable in-game (if operator) or from server console:
op PlayerName
deop PlayerName
tp Player1 Player2
tp X Y Z
kick PlayerName Reason
gamemode survival PlayerName
gamemode creative PlayerName
Reference:
https://minecraft.wiki/w/Commands
192.168.0.108:25565
Best experience with the lowest latency.
10.0.0.2:25565
Works correctly, but due to the VPS relay tunnel latency is higher than LAN.
minecraft.mp4
Since my home server is located on a residential connection, direct remote access doesn't work.
To solve this, I built a private WireGuard relay setup using a VPS with a public IPv4 address.
This creates a secure path to access my home server from anywhere without exposing services directly to the public internet.
This solution fit my requirements best for several reasons:
- my home network is behind CGNAT, so I do not have a true public IPv4 address
- my ISP does not provide IPv6
- I did not want to rely on commercial tunneling solutions such as Cloudflare Tunnel or Tailscale
- I did not want to expose ports/services directly to the internet
- I wanted a private, self-managed, secure solution
Laptop / Phone
│
WireGuard Tunnel
│
Public VPS Relay
│
WireGuard Tunnel
│
ThinkPad Home Server
All traffic between devices is encrypted.
Only authorized peers with valid keys can connect.
I generated all WireGuard keypairs on one trusted device, then securely copied each key to its destination device.
Generate keys:
# Generate VPS keys
wg genkey | tee vps_private.key | wg pubkey > vps_public.key
# Generate Home Server keys
wg genkey | tee home_private.key | wg pubkey > home_public.key
# Generate Remote Device 1 keys
wg genkey | tee device_private.key | wg pubkey > device_public.key
# Generate Remote Device 2 keys
wg genkey | tee mobile_private.key | wg pubkey > mobile_public.keyFor this setup I used a Hetzner VPS because it offers excellent price/performance.
Note: #NotAnAdd
Specs:
- 2 vCPU
- 4 GB RAM
- 40 GB SSD
- 1 Public IPv4
- ~5 EUR/month
I selected the location closest to me for lower latency.
Operating system:
- Debian
Generate SSH key from your device:
ssh-keygen -t ed25519 -C "email@something.com"Upload the public key during VPS deployment.
Then harden SSH:
sudo nano /etc/ssh/sshd_configSet:
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
Restart SSH:
sudo systemctl restart sshThis allows login only with a valid SSH key, which is significantly safer than password authentication.
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -psudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 51820/udp
sudo ufw enableFile:
/etc/wireguard/wg0.conf[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <INSERT_VPS_PRIVATE_KEY>
# Peer: Home Server
[Peer]
PublicKey = <INSERT_HOME_SERVER_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
# Peer: Remote Device1
[Peer]
PublicKey = <INSERT_REMOTE_DEVICE_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
# Peer: Remote Device2
[Peer]
PublicKey = <INSERT_REMOTE_DEVICE_PUBLIC_KEY>
AllowedIPs = 10.0.0.4/32Start:
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0File:
/etc/wireguard/wg0.conf[Interface]
Address = 10.0.0.2/24
PrivateKey = <INSERT_HOME_SERVER_PRIVATE_KEY>
[Peer]
PublicKey = <INSERT_VPS_PUBLIC_KEY>
Endpoint = <VPS_PUBLIC_IP>:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25Why PersistentKeepalive = 25 matters:
Because the server is behind CGNAT, it must maintain an outbound tunnel so the VPS can always reach it.
Start:
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0File:
/etc/wireguard/wg0.conf[Interface]
Address = 10.0.0.3/24
PrivateKey = <INSERT_REMOTE_DEVICE_PRIVATE_KEY>
DNS = 1.1.1.1
[Peer]
PublicKey = <INSERT_VPS_PUBLIC_KEY>
Endpoint = <VPS_PUBLIC_IP>:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25This configuration uses split tunneling.
Only traffic destined for:
10.0.0.0/24
goes through WireGuard.
Everything else uses the normal internet connection.
That means:
- access home services privately through VPN
- normal browsing still uses local internet
Turn VPN on:
sudo wg-quick up wg0Turn VPN off:
sudo wg-quick down wg0Use the official WireGuard app and toggle the tunnel on/off.
sudo wg showShows peers, handshakes, transfer stats.
Ping VPS:
ping 10.0.0.1Ping Home Server:
ping 10.0.0.2Ping VPS:
ping 10.0.0.1Ping Laptop:
ping 10.0.0.3This gives me secure remote access to:
- SSH
- Samba
- Navidrome
- any LAN-only service
without exposing anything publicly.
This section covers the backup setup used in the homelab environment.
The goal is to maintain a secondary copy of important data from the main server on a separate machine inside the network.
The HP acts as the dedicated backup server, while the ThinkPad sends encrypted backups using BorgBackup.
Official website:
BorgBackup is a powerful backup solution featuring:
- deduplication
- compression
- encryption
- incremental backups
Install on both servers:
sudo apt install borgbackupCreate a dedicated backup user:
sudo useradd --system --create-home --shell /bin/bash borguser
sudo passwd borguserSwitch user:
sudo su - borguserCreate backup directory:
mkdir -p ~/backups/MainServerAfter setup is complete, lock password authentication:
sudo passwd -l borguserThis disables password login while still allowing SSH key authentication from the main server.
All commands below are executed as root.
Switch to root:
sudo su -Some directories require root permissions for proper backup access.
ssh-keygen -t ed25519Copy the public key to the backup server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub borguser@192.168.0.109Test connection:
ssh borguser@192.168.0.109borg init --encryption=repokey borguser@192.168.0.109:~/backups/MainServerThis creates an encrypted Borg repository on the backup server.
Create backup script:
sudo nano /usr/local/bin/borg-backup.shContent:
#!/bin/bash
export BORG_REPO="borguser@192.168.0.109:~/backups/MainServer"
export BORG_PASSPHRASE="<YOUR KEY>"
borg create --verbose --stats \
::'{hostname}-{now:%Y-%m-%d_%H:%M}' \
/data
borg prune --verbose --list \
--keep-daily=7 \
--keep-weekly=4 \
--keep-monthly=6
borg compactMake executable:
sudo chmod 700 /usr/local/bin/borg-backup.shManual run:
sudo /usr/local/bin/borg-backup.shEdit root cron jobs:
sudo crontab -eExample daily backup at 03:00 AM:
0 3 * * * /usr/local/bin/borg-backup.sh > /dev/null 2>&1Current backup target:
/data
This includes:
- shared files
- media
- service data
- Docker persistent volumes
The backup server acts as a secondary storage location inside the home network.
- backups are encrypted using Borg's
repokeyencryption - SSH password login is disabled for the backup user


















































