Kasm Windows Agent Will Not Connect
When to use this
Use this when the Windows server workspace (K8) is unreachable from Kasm. Typical symptoms:
No resources are available to create the requested Kasmwhen launching a session- Server shows missing under Admin > Infrastructure > Servers
Last Reportedis hours or days oldlogs.txton the Windows host is growing rapidly
Check the easy thing first: No resources are available is most often a stale session, not an agent problem. The server allows one concurrent user, so an old session (including a forgotten browser tab on another machine) blocks all new ones. Go to Admin > Sessions, clear filters, and delete any session against K8. If the server shows healthy with a current Last Reported, this is your problem, not the agent.
Environment reference
| Item | Value |
|---|---|
| Kasm deployment | 192.168.2.27 (LXC on Proxmox), port 443 |
| Windows server | K8 / k8.home / 192.168.2.28 |
| Server ID | 5d1dc5da-da73-4123-ae19-02559b37d5bd |
| Agent install path | C:\Program Files\Kasm |
| Agent config | C:\Program Files\Kasm\config.yaml |
| Agent log | C:\Program Files\Kasm\logs.txt |
| Agent listen port | 4902 (inbound, from Kasm) |
| Installer | kasm_windows_service_installer_x86_64_1.8_8e3b288d3.exe (pairs with Workspaces 1.19.0) |
Background: why this happens
The agent holds a JWT in config.yaml (agent_jwt_token) with a 3-day lifetime. It refreshes on a rolling schedule. If K8 is powered off or asleep past that window, the token expires and the agent cannot recover on its own - it 403s on every refresh forever.
The per-server registration token from the admin UI is long-lived (10 years) and is only used for enrollment. It does not rescue an expired agent token.
Step 1: Diagnose
On K8 (elevated PowerShell)
Get-Service -Name "Kasm" | Format-List Name,Status,StartType
Get-NetTCPConnection -LocalPort 4902 -State Listen -ErrorAction SilentlyContinue
Get-NetRoute -DestinationPrefix "192.168.2.0/24" | Select-Object InterfaceAlias,NextHop,RouteMetric,InterfaceMetric
Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp|api_host|api_port|registered"
Get-Content "C:\Program Files\Kasm\logs.txt" -Tail 30
Get-Item "C:\Program Files\Kasm\logs.txt" | Select-Object Length,LastWriteTime
Convert the token expiry to a readable date:
$exp = (Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp:\s*(\d+)").Matches.Groups[1].Value
[DateTimeOffset]::FromUnixTimeSeconds([int64]$exp).ToLocalTime()
Test the path to Kasm:
Test-NetConnection -ComputerName 192.168.2.27 -Port 443 -InformationLevel Detailed
On the Kasm LXC (SSH as root)
docker ps --format '{{.Names}}\t{{.Status}}'
docker logs --since 15m kasm_api 2>&1 | grep -iE "register_component|jwt|403|5d1dc5" | tail -40
Confirm the LAN path to the agent:
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/k8.home/4902' && echo "4902 OPEN" || echo "4902 CLOSED/FILTERED"
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/k8.home/3389' && echo "3389 OPEN" || echo "3389 CLOSED/FILTERED"
Reading the results
| Log signature | Cause | Go to |
|---|---|---|
The JWT token has expired + 403 on refresh |
Expired agent token | Step 3 (reinstall) |
ConnectTimeout to an IP |
Agent pointed at a stale address | Step 3 (reinstall with correct host) |
Test-NetConnection shows InterfaceAlias: Tailscale |
Tailscale hijacking the LAN route | Step 2 first |
Service Paused |
nssm gave up after repeated start failures | Step 3 (reinstall) |
| Clean log, service running, healthcheck 200 | Agent is fine | Check for a stale session instead |
Step 2: Fix routing if Tailscale is in the path
K8 and Kasm are both on 192.168.2.0/24. If pfSense advertises that subnet over Tailscale and K8 accepts it, Tailscale wins the route (metric 0/5 vs 256/20) even though the machine sits on that segment physically. Registering through that path is fragile - it breaks whenever Tailscale reshuffles.
On K8 (elevated PowerShell)
tailscale set --accept-routes=false --accept-dns=false
Test-NetConnection -ComputerName 192.168.2.27 -Port 443 -InformationLevel Detailed
You want InterfaceAlias to be the physical NIC (Ethernet 3) and SourceAddress to be 192.168.2.28. These settings persist across reboots, but Tailscale auto-updates, so re-check after a client update.
Step 3: Uninstall and reinstall the agent
This is the fix. Editing config.yaml by hand, dropping in registration_token.txt, or re-running register.exe do not work on an expired agent (see Known Gotchas).
On K8 (elevated PowerShell)
sc.exe stop Kasm
& "C:\Program Files\Kasm\uninstall.exe"
Verify it is gone:
Get-Service -Name "*kasm*" -ErrorAction SilentlyContinue
Test-Path "C:\Program Files\Kasm"
In the Kasm admin UI
- Go to Admin > Infrastructure > Servers, find K8, click Edit.
- Confirm IP/Hostname is
192.168.2.28. - In the Kasm Agent section, copy the Registration Token.
Do not use Server Enrollment Tokens - those create new server records and will orphan the existing one along with its workspace bindings.
On K8 (elevated PowerShell)
Run the installer elevated. Use the GUI so it reports which validation step fails:
- Hostname/IP:
192.168.2.27 - Port:
443 - Registration Token: paste from the admin UI
- Proxy Path: leave blank
Registration validates three things:
- The agent can reach Kasm on the given host and port
- Kasm can reach the agent back on port 4902 at the address in the Server record
- The token is properly signed, unexpired, and issued for this server
Verify
sc.exe query Kasm
Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp|registered"
Get-Content "C:\Program Files\Kasm\logs.txt" -Tail 15
Success looks like:
- Service
RUNNING JWT has been refreshed. New token expires <date ~3 days out>Heartbeat result: Service registered as runningStarting Kasm Upload Server ... On Port 4902GET https /__healthcheck? 200 OK- No 403 errors
Then confirm the Servers page shows K8 healthy with a current Last Reported and the Server ID unchanged, and launch a session.
Step 4: Prevention
The root cause is K8 sleeping past its 3-day token window.
On K8 (elevated PowerShell)
powercfg /change standby-timeout-ac 0
powercfg /hibernate off
Also worth checking periodically:
Get-Item "C:\Program Files\Kasm\logs.txt" | Select-Object Length,LastWriteTime
There is no log rotation. A failing agent writes a line roughly every 1.7 seconds - the last incident produced a 93 MB file in six weeks.
Related: Guac connection proxy token expiry
Different component, different fix. Symptoms are Error, expired JWT flooding kasm_api logs, kasm_guac unhealthy, and browser RDP sessions failing while container workspaces (Chromium, Ubuntu) still work fine.
Container workspaces are KasmVNC and do not touch Guac. RDP to a server workspace does. If only the Windows box is broken, check Guac.
Unlike the Windows agent, Guac can be fixed in place.
In the Kasm admin UI
Copy the Component Registration Token (Global Settings - search the sidebar for "registration"). This is different from the per-server registration token.
On the Kasm LXC (SSH as root)
printf '%s' 'PASTE_TOKEN_HERE' > /opt/kasm/current/conf/app/guac/registration_token.txt
chown kasm:kasm /opt/kasm/current/conf/app/guac/registration_token.txt
chmod 600 /opt/kasm/current/conf/app/guac/registration_token.txt
docker restart kasm_guac
Verify - the token file is consumed and deleted on success:
ls -la /opt/kasm/current/conf/app/guac/registration_token.txt
docker inspect --format '{{.State.Health.Status}} {{.State.Health.FailingStreak}}' kasm_guac
docker logs --since 2m kasm_guac 2>&1 | grep -c 401
Want healthy, FailingStreak 0, and 0 matches. Note that docker logs retains output from before a restart, so use a --since window shorter than the container uptime or you will see stale 401s.
The same registration_token.txt mechanism applies to other components, placed alongside each one's config file:
- Agent:
/opt/kasm/current/conf/app/agent/agent.app.config.yaml - Guac:
/opt/kasm/current/conf/app/guac/kasmguac.app.config.yaml - RDP Gateway:
/opt/kasm/current/conf/app/rdp_gateway/
Known gotchas
These do not work on an expired Windows agent. Confirmed by testing:
- Dropping
registration_token.txtintoC:\Program Files\Kasm. The agent logsLoaded registration token from ...and then submits the expired token fromconfig.yamlanyway. - Running
register.exe(the GUI registration dialog). Registration succeeds server-side and heartbeat starts, but the agent never writes the new token toconfig.yaml, so it keeps 403ing on refresh. - Setting
registered: falseinconfig.yaml. The agent flips it back totrueon startup and changes nothing else.
The Guac registration_token.txt mechanism works. The Windows agent equivalent does not. Do not assume they behave the same way.
Other notes:
- After repeated start failures, nssm leaves the service in
SERVICE_PAUSED. Windows rejects a START control on a paused service. Usesc.exe stop Kasm(orsc.exe continue Kasm), notnssm start. If it will not stop:Stop-Process -Name "agent" -Force. - To park a flapping service while working:
& "C:\Program Files\Kasm\nssm.exe" set Kasm Start SERVICE_DEMAND_START. Remember to set it back toSERVICE_AUTO_START. - The installer version line ("1.8") is not the Workspaces version. Check the compatibility column in the Kasm docs installer table.
- The agent bakes
api_hostintoconfig.yamlat install time. It does not follow the Kasm deployment if it moves. Reinstall after any address change. - Session teardown on server workspaces: Delete Session when done, Leave this session to come back to it. Closing the tab leaves the session running and blocks the single concurrency slot.
Appendix: decoding a JWT in PowerShell
Useful for confirming a token's server_id and expiry.
$t = (Get-Content "C:\Program Files\Kasm\registration_token.txt" -Raw).Trim()
$p = $t.Split('.')[1]
switch ($p.Length % 4) { 2 { $p += '==' } 3 { $p += '=' } }
$json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($p.Replace('-','+').Replace('_','/')))
$json
[DateTimeOffset]::FromUnixTimeSeconds(($json | ConvertFrom-Json).exp).ToLocalTime()
Incident history
| Date | Component | Cause | Fix |
|---|---|---|---|
| 2026-05-22 | Windows agent | Installed pointing at 192.168.2.144; Kasm later migrated to 192.168.2.27 LXC. Silent connect timeouts for months. |
Reinstall against .27 |
| 2026-06-28 | Guac | Token marked consumed during 1.18.1 to 1.19.0 upgrade when refresh errored (known bug, fixed in 1.19.0 but does not revive an already-dead token) | registration_token.txt |
| 2026-09-05 | Windows agent | K8 asleep past the 3-day token window | Uninstall and reinstall |