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:

ComponentRolePort
LibreHardwareMonitorHardware telemetry source8085 (HTTP)
CaddyReverse proxy with TLS termination and authentication8086 (HTTPS)
TailscaleEncrypted overlay networkWireGuard tunnel

Security Layers

LayerProtection
NetworkWindows Firewall with scope-based rules
OverlayTailscale mesh - invisible to public internet
TransportWireGuard encryption (ChaCha20-Poly1305)
ApplicationCaddy internal TLS (self-signed)
AuthenticationHTTP Basic Auth with bcrypt hashing
BindingTailscale IP only - no LAN exposure

Prerequisites

LibreHardwareMonitor Configuration

LibreHardwareMonitor must be configured to expose its web server on the local network.

  1. Download from GitHub Releases
  2. Extract to C:\Tools\LibreHardwareMonitor
  3. Run as Administrator (required for hardware sensor access)
  4. Enable web server: Options > Remote Web Server > IP > Select your LAN IP
  5. 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:

DirectivePurpose
100.0.0.0:8086Bind only to Tailscale interface on port 8086
basicauthRequire username/password authentication
reverse_proxyForward requests to LibreHardwareMonitor
tls internalGenerate self-signed certificate for HTTPS

Step 4: Configure Windows Firewall

The script provides three firewall scope options for different security requirements:

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

RequirementValue
Minimum length8 characters
Hash algorithmbcrypt (Caddy default)
Storageauth.json (admin-only ACL)

Managing Authentication via Script

The interactive menu provides full authentication management:

OptionAction
Enable/Update AuthenticationSet username and password
Change PasswordUpdate password for existing user
Disable AuthenticationRemove 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:

  1. Accepting passwords via SecureString (masked input)
  2. Converting to plain text only for hashing
  3. Immediately clearing plain text from memory
  4. Storing only the bcrypt hash in auth.json
  5. Restricting file permissions to Administrators and SYSTEM only

Firewall Management

Scope Options

ScopeRemote AddressProfileSecurity Level
TailscaleOnly100.64.0.0/10AnyHighest
LANUser-specifiedPrivate,DomainMedium
AnyAnyAnyLowest

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:

OptionAction
[1] Tailscale OnlyRestrict to 100.64.0.0/10
[2] LAN RestrictedSpecify addresses interactively
[3] AnyAllow all (with confirmation)
[4] Remove All RulesDelete all Caddy firewall rules
[5] Refresh StatusView 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

ActionCommand
StartStart-Service Caddy
StopStop-Service Caddy
RestartRestart-Service Caddy
StatusGet-Service Caddy
Deletesc.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

  1. Download latest release from: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/releases
  2. Extract ZIP to C:\LibreHardwareMonitor
  3. Run LibreHardwareMonitor.exe as Administrator

Alternatively, install via WinGet:

winget install LibreHardwareMonitor.LibreHardwareMonitor

Configure Web Server

  1. Open LibreHardwareMonitor
  2. Options > Remote Web Server > Run (enable checkbox)
  3. Options > Remote Web Server > Port: 8085
  4. 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

  1. Options > Run On Windows Startup (if available)
  2. Options > Start Minimized
  3. Options > Minimize To Tray
  4. 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.

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

  1. Verify LHM is running: Get-Process -Name "LibreHardwareMonitor"
  2. Check port binding: Get-NetTCPConnection -LocalPort 8085 -State Listen
  3. Confirm correct IP selected in Options > Remote Web Server > IP
  4. Restart LHM after changing web server settings

Bound to Wrong IP

If bound to Hyper-V IP (172.x.x.x):

  1. Options > Remote Web Server > IP > Select correct LAN IP
  2. Uncheck then re-check Run to restart web server

Settings Not Persisting

  1. Close LHM properly via tray icon > Exit (not Task Manager kill)
  2. Check config file exists: Test-Path "C:\LibreHardwareMonitor\LibreHardwareMonitor.config"
  3. Check file is not read-only when trying to save

Watchdog Not Starting LHM

  1. Verify path is correct in task
  2. Check task is running with correct principal (SYSTEM for background, username for interactive)
  3. 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

The interactive menu provides comprehensive management:

KeyOptionDescription
1Install Caddy ProxyFull installation with auth and firewall prompts
2Uninstall CaddyRemove service, files, firewall rules
3Test InstallationVerify all components
4Restart Caddy ServiceRestart after config changes
5View CaddyfileDisplay current configuration
6Edit CaddyfileOpen in Notepad
7LHM Setup InstructionsDisplay setup guide
8Open Install FolderOpen C:\Caddy in Explorer
AAuthentication SettingsManage auth credentials
FFirewall SettingsManage firewall rules
SScan/Select Upstream IPFind LHM on local interfaces
QQuitExit 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

SymptomHTTP CodeCauseResolution
Connection RefusedN/AFirewall or service not runningCheck Get-Service Caddy and firewall rules
Bad Gateway502Upstream unreachableVerify LHM is running on correct IP/port
Certificate ErrorN/ASelf-signed certificateAccept warning or install root CA
401 Unauthorized401Wrong credentialsCheck username/password or reset via menu
Blank Page200JavaScript/header mismatchUpdate 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:

ApproachProsCons
Program-basedSimple, follows executableAllows all connections if program runs
Port + AddressGranular control, explicit allow listRequires 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)

For maximum security, combine all layers:

.\Setup-CaddyProxy.ps1 -Action Install -EnableAuth -FirewallScope TailscaleOnly

This provides:

  1. Firewall blocks all non-Tailscale traffic (100.64.0.0/10 only)
  2. Tailscale provides authenticated mesh network access
  3. TLS encrypts all traffic
  4. 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.

Final References