Overview
The convergence of home lab infrastructure, remote systems administration, and Zero Trust networking principles has necessitated robust solutions for exposing internal telemetry without compromising security. This guide provides a comprehensive implementation for securing LibreHardwareMonitor using Caddy as a reverse proxy and Tailscale as the transport layer.
The architecture moves beyond simple port forwarding, utilizing an overlay network (Tailscale) to render the service invisible to the public internet, while employing application-layer encryption (TLS) via Caddy. Version 5.0 adds HTTP Basic Authentication and granular firewall rule management, adhering to the principle of Defence in Depth.
Architecture Overview
The solution integrates three distinct technologies with multiple security layers:
| Component | Role | Port |
|---|---|---|
| LibreHardwareMonitor | Hardware telemetry source | 8085 (HTTP) |
| Caddy | Reverse proxy with TLS termination and authentication | 8086 (HTTPS) |
| Tailscale | Encrypted overlay network | WireGuard tunnel |
Security Layers
| Layer | Protection |
|---|---|
| Network | Windows Firewall with scope-based rules |
| Overlay | Tailscale mesh - invisible to public internet |
| Transport | WireGuard encryption (ChaCha20-Poly1305) |
| Application | Caddy internal TLS (self-signed) |
| Authentication | HTTP Basic Auth with bcrypt hashing |
| Binding | Tailscale IP only - no LAN exposure |
Prerequisites
LibreHardwareMonitor Configuration
LibreHardwareMonitor must be configured to expose its web server on the local network.
- Download from GitHub Releases
- Extract to
C:\Tools\LibreHardwareMonitor - Run as Administrator (required for hardware sensor access)
- Enable web server: Options > Remote Web Server > IP > Select your LAN IP
- Enable web server: Options > Remote Web Server > Run
Verify the upstream is accessible locally:
Test-NetConnection -ComputerName 10.1.10.30 -Port 8085
Tailscale Installation
Ensure Tailscale is installed and authenticated on the host system. Verify your Tailscale IP:
tailscale ip -4
Example output: 100.0.0.0
Implementation
Quick Start
For automated deployment with recommended security settings:
.\Setup-CaddyProxy.ps1 -Action Install -EnableAuth -FirewallScope TailscaleOnly
This enables authentication (prompts for credentials) and restricts firewall access to Tailscale devices only.
Step 1: Create Directory Structure
New-Item -ItemType Directory -Path "C:\Caddy" -Force
New-Item -ItemType Directory -Path "C:\Caddy\data" -Force
New-Item -ItemType Directory -Path "C:\Caddy\logs" -Force
Step 2: Download Caddy
Invoke-WebRequest -Uri "https://caddyserver.com/api/download?os=windows&arch=amd64" -OutFile "C:\Caddy\caddy.exe"
Verify installation:
C:\Caddy\caddy.exe version
Step 3: Create Caddyfile
Open Notepad to create the configuration:
notepad C:\Caddy\Caddyfile
Paste the following configuration (without authentication):
100.0.0.0:8086 {
reverse_proxy 10.1.10.30:8085
tls internal
}
Or with HTTP Basic Authentication:
100.0.0.0:8086 {
basicauth {
admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
}
reverse_proxy 10.1.10.30:8085
tls internal
}
Generate a password hash using Caddy:
"YourPassword" | C:\Caddy\caddy.exe hash-password
Configuration breakdown:
| Directive | Purpose |
|---|---|
100.0.0.0:8086 | Bind only to Tailscale interface on port 8086 |
basicauth | Require username/password authentication |
reverse_proxy | Forward requests to LibreHardwareMonitor |
tls internal | Generate self-signed certificate for HTTPS |
Step 4: Configure Windows Firewall
The script provides three firewall scope options for different security requirements:
Tailscale Only (Recommended)
Restricts access to the Tailscale CGNAT range (100.64.0.0/10):
New-NetFirewallRule -DisplayName "Caddy Reverse Proxy (Tailscale Only)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8086 `
-RemoteAddress 100.64.0.0/10 `
-Action Allow `
-Profile Any
LAN Restricted
Allows specific IP addresses or subnets:
New-NetFirewallRule -DisplayName "Caddy Reverse Proxy (LAN Restricted)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8086 `
-RemoteAddress 10.1.10.0/24 `
-Action Allow `
-Profile Private,Domain
Any (Least Secure)
Allows all connections (not recommended for production):
New-NetFirewallRule -DisplayName "Caddy Reverse Proxy (Any)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8086 `
-Action Allow `
-Profile Any
Step 5: Test Configuration
Run Caddy in foreground mode to verify the configuration:
C:\Caddy\caddy.exe run --config C:\Caddy\Caddyfile
Expected output:
INFO admin admin endpoint started
INFO tls.cache.maintenance started background certificate maintenance
INFO http.log server running
INFO autosaved config
INFO serving initial configuration
Test from another terminal:
Test-NetConnection -ComputerName 100.0.0.0 -Port 8086
Step 6: Install as Windows Service
Stop the foreground process (Ctrl+C), then create the service:
sc.exe create Caddy start= auto binPath= "C:\Caddy\caddy.exe run --config C:\Caddy\Caddyfile"
Important: Note the space after each equals sign - this is required by sc.exe syntax.
Configure automatic restart on failure:
sc.exe failure Caddy reset= 86400 actions= restart/60000/restart/60000/restart/60000
This configures:
- Reset failure count after 24 hours (86400 seconds)
- Restart after 60 seconds on first, second, and subsequent failures
Start the service:
Start-Service Caddy
Authentication
How It Works
Caddy’s basicauth directive provides HTTP Basic Authentication with bcrypt-hashed passwords. The script uses Caddy’s built-in hash-password command to generate secure hashes, ensuring passwords are never stored in plain text.
Password Requirements
| Requirement | Value |
|---|---|
| Minimum length | 8 characters |
| Hash algorithm | bcrypt (Caddy default) |
| Storage | auth.json (admin-only ACL) |
Managing Authentication via Script
The interactive menu provides full authentication management:
| Option | Action |
|---|---|
| Enable/Update Authentication | Set username and password |
| Change Password | Update password for existing user |
| Disable Authentication | Remove auth and regenerate Caddyfile |
Command-line usage:
# Enable during install
.\Setup-CaddyProxy.ps1 -Action Install -EnableAuth -AuthUsername "monitor"
# With custom username
.\Setup-CaddyProxy.ps1 -Action Install -EnableAuth -AuthUsername "admin"
Security Considerations for Authentication
The script handles password security by:
- Accepting passwords via
SecureString(masked input) - Converting to plain text only for hashing
- Immediately clearing plain text from memory
- Storing only the bcrypt hash in
auth.json - Restricting file permissions to Administrators and SYSTEM only
Firewall Management
Scope Options
| Scope | Remote Address | Profile | Security Level |
|---|---|---|---|
| TailscaleOnly | 100.64.0.0/10 | Any | Highest |
| LAN | User-specified | Private,Domain | Medium |
| Any | Any | Any | Lowest |
Command-Line Usage
# Tailscale only (default, most secure)
.\Setup-CaddyProxy.ps1 -Action Install -FirewallScope TailscaleOnly
# LAN subnet
.\Setup-CaddyProxy.ps1 -Action Install -FirewallScope LAN -AllowedAddresses "10.1.10.0/24"
# Specific hosts
.\Setup-CaddyProxy.ps1 -Action Install -FirewallScope LAN -AllowedAddresses "10.1.10.10,10.1.10.30"
# Mixed (subnet + specific IPs)
.\Setup-CaddyProxy.ps1 -Action Install -FirewallScope LAN -AllowedAddresses "10.1.10.0/24,192.168.1.50"
Interactive Menu
The firewall submenu ([F] from main menu) provides:
| Option | Action |
|---|---|
| [1] Tailscale Only | Restrict to 100.64.0.0/10 |
| [2] LAN Restricted | Specify addresses interactively |
| [3] Any | Allow all (with confirmation) |
| [4] Remove All Rules | Delete all Caddy firewall rules |
| [5] Refresh Status | View current rule configuration |
Viewing Current Rules
Get-NetFirewallRule -DisplayName "Caddy Reverse Proxy*" | ForEach-Object {
$port = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $_
$addr = Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $_
[PSCustomObject]@{
Name = $_.DisplayName
Enabled = $_.Enabled
Port = $port.LocalPort
RemoteAddress = $addr.RemoteAddress
Profile = $_.Profile
}
} | Format-Table -AutoSize
Verification
Service Status
Get-Service Caddy
Port Listening
Get-NetTCPConnection -LocalPort 8086 -State Listen | Format-Table LocalAddress, LocalPort
Expected output:
LocalAddress LocalPort
------------ ---------
100.0.0.0 8086
Browser Test
From any Tailscale-connected device, navigate to:
https://100.0.0.0:8086/
If authentication is enabled, you will be prompted for credentials. Accept the certificate warning (expected for self-signed certificates), and the LibreHardwareMonitor dashboard should load.
Service Management
| Action | Command |
|---|---|
| Start | Start-Service Caddy |
| Stop | Stop-Service Caddy |
| Restart | Restart-Service Caddy |
| Status | Get-Service Caddy |
| Delete | sc.exe delete Caddy |
Local Scheduled Task setup
LibreHardwareMonitor Setup Guide
Configuration guide for LibreHardwareMonitor with persistent settings and automatic restart watchdog.
Repository: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor
Download and Install
- Download latest release from: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/releases
- Extract ZIP to
C:\LibreHardwareMonitor - Run
LibreHardwareMonitor.exeas Administrator
Alternatively, install via WinGet:
winget install LibreHardwareMonitor.LibreHardwareMonitor
Configure Web Server
- Open LibreHardwareMonitor
- Options > Remote Web Server > Run (enable checkbox)
- Options > Remote Web Server > Port:
8085 - Options > Remote Web Server > IP: Select your LAN IP
Avoid selecting Hyper-V virtual switch IPs (172.x.x.x range). Choose your physical NIC IP.
Configure Startup Behaviour
- Options > Run On Windows Startup (if available)
- Options > Start Minimized
- Options > Minimize To Tray
- Options > Minimize On Close
Verify Web Server
Test-NetConnection -ComputerName localhost -Port 8085
Browser test:
http://localhost:8085/
Watchdog Task
Creates a scheduled task that checks every 5 minutes and restarts LHM if not running.
Option A: Interactive Mode (Recommended)
Runs in your desktop session with visible tray icon. Use this if you need to access LHM settings or view the GUI.
$LHMPath = "C:\LibreHardwareMonitor"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-WindowStyle Hidden -Command `"if (-not (Get-Process -Name 'LibreHardwareMonitor' -ErrorAction SilentlyContinue)) { Start-Process '$LHMPath\LibreHardwareMonitor.exe' -WorkingDirectory '$LHMPath' }`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 9999)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Restarts LHM if not running (interactive)" -Force
Option B: Background Mode (Headless)
Runs as SYSTEM in session 0. No tray icon visible. Use for servers or headless monitoring where GUI access is not required.
$LHMPath = "C:\LibreHardwareMonitor"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-WindowStyle Hidden -Command `"if (-not (Get-Process -Name 'LibreHardwareMonitor' -ErrorAction SilentlyContinue)) { Start-Process '$LHMPath\LibreHardwareMonitor.exe' -WorkingDirectory '$LHMPath' }`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 9999)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Restarts LHM if not running (background)" -Force
Start Watchdog Immediately
Start-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog"
Test Watchdog
# Stop LHM
Stop-Process -Name "LibreHardwareMonitor" -Force -ErrorAction SilentlyContinue
# Trigger watchdog
Start-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog"
# LHM should restart (check tray for Option A, or process list for Option B)
Get-Process -Name "LibreHardwareMonitor"
Verify Watchdog
Get-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog" | Select-Object TaskName, State
Remove Watchdog
Unregister-ScheduledTask -TaskName "LibreHardwareMonitor-Watchdog" -Confirm:$false
Persistent Settings
Settings are stored in LibreHardwareMonitor.config in the application folder.
Lock Settings (Read-Only)
Prevents accidental changes after configuration is complete:
Set-ItemProperty -Path "C:\LibreHardwareMonitor\LibreHardwareMonitor.config" -Name IsReadOnly -Value $true
Unlock Settings
Required before making changes:
Set-ItemProperty -Path "C:\LibreHardwareMonitor\LibreHardwareMonitor.config" -Name IsReadOnly -Value $false
Backup Settings
Copy-Item "C:\LibreHardwareMonitor\LibreHardwareMonitor.config" "C:\LibreHardwareMonitor\LibreHardwareMonitor.config.bak"
Restore Settings
Copy-Item "C:\LibreHardwareMonitor\LibreHardwareMonitor.config.bak" "C:\LibreHardwareMonitor\LibreHardwareMonitor.config" -Force
Verification Commands
Check LHM process:
Get-Process -Name "LibreHardwareMonitor" -ErrorAction SilentlyContinue
Check web server port:
Get-NetTCPConnection -LocalPort 8085 -State Listen -ErrorAction SilentlyContinue
Check which IP the web server is bound to:
Get-NetTCPConnection -LocalPort 8085 -State Listen | Select-Object LocalAddress, LocalPort
Troubleshooting
Web Server Not Responding
- Verify LHM is running:
Get-Process -Name "LibreHardwareMonitor" - Check port binding:
Get-NetTCPConnection -LocalPort 8085 -State Listen - Confirm correct IP selected in Options > Remote Web Server > IP
- Restart LHM after changing web server settings
Bound to Wrong IP
If bound to Hyper-V IP (172.x.x.x):
- Options > Remote Web Server > IP > Select correct LAN IP
- Uncheck then re-check Run to restart web server
Settings Not Persisting
- Close LHM properly via tray icon > Exit (not Task Manager kill)
- Check config file exists:
Test-Path "C:\LibreHardwareMonitor\LibreHardwareMonitor.config" - Check file is not read-only when trying to save
Watchdog Not Starting LHM
- Verify path is correct in task
- Check task is running with correct principal (SYSTEM for background, username for interactive)
- View task history in Task Scheduler for errors
LHM Running But No Tray Icon
This occurs when using Background Mode (Option B). The process runs in session 0 (SYSTEM) which is isolated from your desktop. Switch to Interactive Mode (Option A) if you need tray access.
Directory Structure
C:\LibreHardwareMonitor\
├── LibreHardwareMonitor.exe
├── LibreHardwareMonitor.config
├── LibreHardwareMonitorLib.dll
└── [other DLLs]
Integration with Caddy Proxy
After configuring LHM, use the Caddy proxy script for secure Tailscale access:
.\Setup-CaddyProxy.ps1 -Action Install -UpstreamIP 127.0.0.1 -Force
Access via Tailscale:
https://<tailscale-ip>:8086/
References
- LibreHardwareMonitor GitHub: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor
- LibreHardwareMonitor Releases: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/releases
- Scheduled Tasks: https://learn.microsoft.com/en-us/powershell/module/scheduledtasks/register-scheduledtask
- WinGet: https://learn.microsoft.com/en-us/windows/package-manager/winget/
Menu Options
The interactive menu provides comprehensive management:
| Key | Option | Description |
|---|---|---|
| 1 | Install Caddy Proxy | Full installation with auth and firewall prompts |
| 2 | Uninstall Caddy | Remove service, files, firewall rules |
| 3 | Test Installation | Verify all components |
| 4 | Restart Caddy Service | Restart after config changes |
| 5 | View Caddyfile | Display current configuration |
| 6 | Edit Caddyfile | Open in Notepad |
| 7 | LHM Setup Instructions | Display setup guide |
| 8 | Open Install Folder | Open C:\Caddy in Explorer |
| A | Authentication Settings | Manage auth credentials |
| F | Firewall Settings | Manage firewall rules |
| S | Scan/Select Upstream IP | Find LHM on local interfaces |
| Q | Quit | Exit menu |
File Structure
C:\Caddy\
├── caddy.exe # Caddy binary
├── Caddyfile # Proxy configuration
├── auth.json # Authentication config (restricted ACL)
├── firewall.json # Firewall scope config
├── data\ # Caddy data directory
└── logs\ # Log files
Troubleshooting
Common Issues
| Symptom | HTTP Code | Cause | Resolution |
|---|---|---|---|
| Connection Refused | N/A | Firewall or service not running | Check Get-Service Caddy and firewall rules |
| Bad Gateway | 502 | Upstream unreachable | Verify LHM is running on correct IP/port |
| Certificate Error | N/A | Self-signed certificate | Accept warning or install root CA |
| 401 Unauthorized | 401 | Wrong credentials | Check username/password or reset via menu |
| Blank Page | 200 | JavaScript/header mismatch | Update LibreHardwareMonitor |
Diagnostic Commands
Check if Caddy is listening:
netstat -ano | findstr ":8086"
View Caddy process:
Get-Process -Name caddy -ErrorAction SilentlyContinue | Format-Table Id, ProcessName, Path
Test upstream connectivity:
Invoke-RestMethod -Uri "http://10.1.10.30:8085/data.json" | ConvertTo-Json -Depth 3
Check firewall rules:
Get-NetFirewallRule -DisplayName "Caddy*" | Select-Object DisplayName, Enabled, Direction, Action
Verify authentication config:
Get-Content C:\Caddy\Caddyfile | Select-String "basicauth" -Context 0,3
Updating Caddy
Caddy is distributed as a static binary, making updates straightforward:
Stop-Service Caddy
Invoke-WebRequest -Uri "https://caddyserver.com/api/download?os=windows&arch=amd64" -OutFile "C:\Caddy\caddy.exe"
Start-Service Caddy
Uninstallation
# Stop and remove service
Stop-Service Caddy -ErrorAction SilentlyContinue
sc.exe delete Caddy
# Remove firewall rules
Remove-NetFirewallRule -DisplayName "Caddy Reverse Proxy*"
# Remove files
Remove-Item -Path "C:\Caddy" -Recurse -Force
Or use the script:
.\Setup-CaddyProxy.ps1 -Action Uninstall -Force
Security Considerations
Why Tailscale-Only Binding?
By binding Caddy exclusively to the Tailscale IP (100.0.0.0), the service is inaccessible from:
- The local LAN (unless devices are on Tailscale)
- The public internet (Tailscale IPs are not routable)
- Port scanners (Shodan, Censys)
Why Scope-Based Firewall Rules?
The script creates port-based rules with address filtering rather than program-based rules:
| Approach | Pros | Cons |
|---|---|---|
| Program-based | Simple, follows executable | Allows all connections if program runs |
| Port + Address | Granular control, explicit allow list | Requires address management |
For Tailscale-only deployments, restricting to 100.64.0.0/10 ensures only Tailnet devices can connect, even if the firewall rule is accidentally modified.
Internal TLS Rationale
While Tailscale already encrypts traffic using WireGuard, adding application-layer TLS provides:
- End-to-end encryption where the private key is held by Caddy
- Protection against potential malicious actors within the tailnet
- Defence in depth if the VPN tunnel is somehow compromised
Authentication Rationale
HTTP Basic Authentication adds a credential layer that:
- Prevents unauthorized access even if someone gains Tailnet access
- Provides audit capability (who authenticated)
- Allows different credentials per deployment
- Uses bcrypt hashing (computationally expensive to brute force)
Recommended Configuration
For maximum security, combine all layers:
.\Setup-CaddyProxy.ps1 -Action Install -EnableAuth -FirewallScope TailscaleOnly
This provides:
- Firewall blocks all non-Tailscale traffic (100.64.0.0/10 only)
- Tailscale provides authenticated mesh network access
- TLS encrypts all traffic
- basicauth requires username/password
Alternative: Tailscale HTTPS Certificates
Caddy can use Tailscale’s built-in certificate provisioning:
100.0.0.0:8086 {
basicauth {
admin $2a$14$hashedpassword...
}
reverse_proxy 10.1.10.30:8085
tls {
get_certificate tailscale
}
}
This provides valid Let’s Encrypt certificates with no browser warnings, but requires additional Tailscale socket permissions on Windows.
Full Script
Presented using CodeMirror for a more complete visualization, this was an off the cuff project that I had an idea to a while ago but never followed through with. I have always felt that HW Info is limited when it comes to remote HW telemetry even though it is a truly excellent piece of software.
This is a simple & secure solution to remotely monitor your PC / Server hardware details. The requirements are Tailscale and Caddy, with optional HTTP Basic Authentication and granular firewall controls.

