Share a locally attached USB device (e.g. a license dongle or USB mass storage key) from one Windows machine to another so it appears as a native physical USB device on the remote end.
Stack used:
- Host side: usbipd-win — shares the USB device over the network
- Client side: usbip-win2 — attaches the remote USB device as if it were locally plugged in
- Optional networking: Tailscale — simplifies connectivity across networks and eliminates firewall configuration
- Prerequisites
- Architecture Overview
- Host Setup (Machine with the USB device)
- Client Setup (Remote machine)
- Using Tailscale (Recommended for cross-network or domain scenarios)
- Domain-Joined Host: GPO Firewall Considerations
- Detaching and Cleanup
- Reconnecting After Reboot
- Troubleshooting
- Security Considerations
- Quick Reference Cheatsheet
| Requirement | Host | Client |
|---|---|---|
| Windows 10 (1903+) or Windows 11 | ✅ | ✅ |
| Administrator / elevated PowerShell | ✅ | ✅ |
| Network connectivity between machines | ✅ | ✅ |
| USB device physically plugged in | ✅ | — |
| usbipd-win installed | ✅ | — |
| usbip-win2 installed | — | ✅ |
Note: Both machines must be able to reach each other over TCP port 3240 (standard USB/IP protocol port), unless using Tailscale or the SSH tunnel workaround described below.
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ HOST MACHINE │ │ CLIENT MACHINE │
│ │ │ │
│ ┌─────────────┐ │ TCP │ ┌─────────────┐ │
│ │ USB Dongle │──► usbipd-win ─┼───3240──┼──► usbip-win2 │ Virtual USB │ │
│ │ (physical) │ │ │ │ Device │ │
│ └─────────────┘ │ │ └─────────────┘ │
└─────────────────────────────────┘ └─────────────────────────────────┘
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ HOST MACHINE │ │ CLIENT MACHINE │
│ │ │ │
│ ┌─────────────┐ │Tailscale│ ┌─────────────┐ │
│ │ USB Dongle │──► usbipd-win ─┼─ VPN ───┼──► usbip-win2 │ Virtual USB │ │
│ │ (physical) │ │ :3240 │ │ Device │ │
│ └─────────────┘ │ │ └─────────────┘ │
└─────────────────────────────────┘ └─────────────────────────────────┘
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ HOST MACHINE │ │ CLIENT MACHINE │
│ │ │ │
│ ┌─────────────┐ │ SSH │ SSH tunnel ┌─────────────┐ │
│ │ USB Dongle │──► usbipd-win ─┼───22────┼──► localhost ─► usbip-win2 │ │
│ │ (physical) │ │ │ :3240 │ Virtual USB │ │
│ └─────────────┘ │ │ └─────────────┘ │
└─────────────────────────────────┘ └─────────────────────────────────┘
All commands below must be run in an elevated (Administrator) PowerShell.
Option A — via winget (recommended):
winget install usbipdOption B — manual installer:
Download the latest .msi from the usbipd-win releases page and run it.
After installation, close and reopen PowerShell so the new PATH entry takes effect.
Verify the install:
usbipd --versionusbipd-win installs a Windows Service that starts automatically. You do not need to run
usbipd servermanually.
List all connected USB devices:
usbipd listExample output:
BUSID VID:PID DEVICE STATE
2-9 0781:5571 USB Mass Storage Device Not shared
3-1 046d:c52b USB Input Device Not shared
Note the BUSID of your device (e.g. 2-9). Bind it — this marks it as available for sharing and persists across reboots:
usbipd bind --busid 2-9Verify the state changed:
usbipd list
# STATE column should now show: Shared
bindonly needs to be run once. The binding and the usbipd service both persist across reboots automatically.
Add a local inbound rule for port 3240:
New-NetFirewallRule `
-DisplayName "usbipd USB/IP" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 3240 `
-Action Allow `
-Profile Domain,Private,PublicVerify the rule was created and is active:
Get-NetFirewallRule -DisplayName "usbipd USB/IP" | `
Select-Object DisplayName, Enabled, Action, PolicyStoreSourceTypeExpected output:
DisplayName Enabled Action PolicyStoreSourceType
----------- ------- ------ ---------------------
usbipd USB/IP True Allow Local
If the host is domain-joined, check whether GPO is overriding local firewall rules:
netsh advfirewall show allprofilesIf you see LocalFirewallRules: N/A (GPO-store only) under any profile, local rules are completely ignored by GPO and the rule above will have no effect. See Domain-Joined Host: GPO Firewall Considerations or use Tailscale to bypass this entirely.
Find the host's IP address (needed for the client):
ipconfig
# Note the IPv4 Address of the relevant network adapter
# If using Tailscale, use the Tailscale IP (100.x.x.x) insteadFinal confirmation:
usbipd list
# STATE column should read: SharedThe host is fully configured.
All commands below must be run in an elevated (Administrator) PowerShell.
Download the latest release installer from the usbip-win2 releases page.
Run the installer. It installs a kernel-mode driver, so a reboot is required after installation.
After rebooting, verify:
usbip --versionBefore attempting to attach, verify port 3240 is reachable from the client:
Test-NetConnection -ComputerName <HOST-IP> -Port 3240TcpTestSucceeded : True→ proceed to attach normallyTcpTestSucceeded : False→ port is blocked; use Tailscale or see Domain-Joined Host: GPO Firewall Considerations
Replace <HOST-IP> with the host's IP address and <BUSID> with the bus ID shown on the host (e.g. 2-9):
usbip attach -r <HOST-IP> -b <BUSID>Example:
usbip attach -r 192.168.1.100 -b 2-9If using Tailscale, use the Tailscale IP of the host instead:
usbip attach -r 100.x.x.x -b 2-9# List attached USB/IP devices
usbip portThe USB device should appear in Device Manager exactly as it would if physically plugged in. Your licensed software should detect it normally.
Tailscale is a zero-config VPN built on WireGuard. Installing it on both machines creates a private encrypted network between them regardless of what physical network or domain they are on. This solves several problems at once:
- Bypasses GPO firewall restrictions — Tailscale creates its own virtual network adapter. Corporate GPO manages the Domain/Private/Public firewall profiles but does not control the Tailscale interface, so port 3240 can be opened on that interface without any GPO conflict.
- Works across different networks — host on a corporate LAN, client on home Wi-Fi, or anywhere else — Tailscale handles the routing transparently.
- Stable IP addresses — each machine gets a permanent Tailscale IP (in the
100.x.x.xrange) that never changes, so you never have to update connection settings when the LAN IP changes. - Encrypted by default — all traffic between machines is encrypted via WireGuard.
- No credentials stored on the client — Tailscale uses OAuth via your identity provider. No passwords are stored on the machine. Access is controlled via pre-auth keys and device approval (see below).
- Free for personal use — Tailscale's free tier supports up to 100 devices.
On both machines — install Tailscale:
winget install Tailscale.TailscaleOr download from https://tailscale.com/download.
On the host machine — sign in with your account:
After installation, Tailscale opens a browser window. Sign in with Google, Microsoft, or GitHub. This is your account — the one you control.
On the client machine — do not sign in directly. Instead, use a pre-auth key (see below).
The remote user on the client machine should never sign in with your identity provider credentials. Instead, use Tailscale's pre-auth key system combined with device approval so you retain full control at every step.
In the Tailscale admin panel, require your explicit approval before any new device can join your network:
- Go to https://login.tailscale.com/admin/machines
- Click Settings → Device approval
- Enable Require approval for new devices
With this enabled, any new machine that tries to join sits in a pending state until you approve it. You receive an email notification and nothing connects until you click approve.
A pre-auth key lets the remote user join your tailnet without needing your identity provider credentials.
- Go to https://login.tailscale.com/admin/settings/keys
- Click Generate auth key
- Configure it:
- One-time use — key expires after first use, cannot be reused
- Expiry — set a short window (e.g. 1–4 hours) so it's useless if intercepted
- Tags — optionally tag the device (e.g.
tag:usb-client) for ACL control later
- Copy the key and send it to the remote user securely (Teams, Signal, etc.)
On the client machine, instead of clicking "Sign in", the remote user opens a command prompt and runs:
tailscale up --authkey=<PASTE-KEY-HERE>The machine joins your tailnet in a pending state — it cannot communicate with anything yet.
You receive an email notification. Go to the admin panel, review the new device, and click Approve. Only after your approval does the device become active on your tailnet.
# On the host
tailscale status
# The client machine should appear in the list with its 100.x.x.x IP# On the client
tailscale status
ping 100.x.x.x # host's Tailscale IPBy default, all devices on a tailnet can reach each other. You can lock this down so the client machine can only reach the host on port 3240 and nothing else.
In the Tailscale admin panel, go to Access Controls and edit the ACL policy:
{
"tagOwners": {
"tag:usb-host": ["autogroup:owner"],
"tag:usb-client": ["autogroup:owner"]
},
"acls": [
{
"action": "accept",
"src": ["tag:usb-client"],
"dst": ["tag:usb-host:3240"]
}
]
}This allows the client to reach the host on port 3240 only — nothing else on your tailnet is accessible from the client machine.
To apply tags, go to the admin panel → Machines → click the machine → Edit tags.
Even with Tailscale, you still need a local firewall rule on the host. Scope it exclusively to the Tailscale interface rather than the corporate LAN — this avoids GPO interference and limits exposure:
New-NetFirewallRule `
-DisplayName "usbipd USB/IP (Tailscale)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 3240 `
-Action Allow `
-InterfaceAlias "Tailscale"Then attach from the client using the host's Tailscale IP:
usbip attach -r 100.x.x.x -b 2-9If the host machine is joined to a corporate domain and Tailscale is not an option, Group Policy may manage the Windows Firewall and block inbound connections even when a local allow rule exists.
Step 1 — Check effective firewall policy:
netsh advfirewall show allprofilesIf you see LocalFirewallRules: N/A (GPO-store only) under any profile, GPO has taken full control and local rules are completely ignored — any rule you add via PowerShell or the firewall UI will have no effect.
Step 2 — Check the full GPO report:
gpresult /h "$env:USERPROFILE\gpo-report.html"
Start-Process "$env:USERPROFILE\gpo-report.html"Look for the Windows Firewall with Advanced Security section. Key warning signs:
Inbound connections: Blockunder Domain/Private/Public profile settingsApply local firewall rules: Not Configured
Step 3 — Test from the client:
Test-NetConnection -ComputerName <HOST-IP> -Port 3240If TcpTestSucceeded : False, use Tailscale or one of the options below.
Ask your domain administrator to make one of the following changes via GPO for your machine or OU:
- Set Apply local firewall rules to Enabled for the Domain profile in the Windows Firewall policy, or
- Add an explicit GPO inbound rule allowing TCP 3240 for your machine
This is a minimal change, easily justified for a developer workstation.
If GPO cannot be changed and Tailscale is not permitted, tunnel usbipd traffic through SSH on port 22, which is typically allowed in corporate environments.
On the host — enable OpenSSH Server:
# Check if already installed
Get-WindowsCapability -Online -Name OpenSSH.Server*
# Install if not present
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Start and set to automatic
Start-Service sshd
Set-Service -Name sshd -StartupType AutomaticVerify SSH is listening:
netstat -an | findstr :22On the client — create the SSH tunnel:
Open a PowerShell window and run:
ssh -L 3240:localhost:3240 <USERNAME>@<HOST-IP>Keep this window open — the tunnel stays active as long as this session is running.
On the client — attach via localhost:
Open a second elevated PowerShell window:
usbip attach -r localhost -b <BUSID>To make the SSH tunnel persistent, create a scheduled task that re-establishes the tunnel at logon:
$action = New-ScheduledTaskAction -Execute "ssh.exe" ` -Argument "-N -L 3240:localhost:3240 <USERNAME>@<HOST-IP>" $trigger = New-ScheduledTaskTrigger -AtLogOn $principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -RunLevel Highest Register-ScheduledTask -TaskName "USB-IP SSH Tunnel" ` -Action $action -Trigger $trigger -Principal $principal
List attached ports:
usbip portExample output:
Port 00: <Port in Use> at Full Speed(12Mbps)
: unknown vendor : unknown product (0781:5571)
2-9 -> usbip://192.168.1.100:3240/2-9
Detach using the port number shown:
usbip detach -p 00If using the SSH tunnel, you can now close the tunnel session.
To stop sharing the device permanently:
usbipd unbind --busid 2-9To remove the firewall rule:
Remove-NetFirewallRule -DisplayName "usbipd USB/IP"
# Or if using Tailscale-scoped rule:
Remove-NetFirewallRule -DisplayName "usbipd USB/IP (Tailscale)"To immediately cut off the client machine from your tailnet:
- Go to https://login.tailscale.com/admin/machines
- Find the client machine
- Click Delete or Disable — access is revoked instantly
Nothing to do. The usbipd service and Tailscale both start automatically. The device will be in Shared state after every reboot as long as it is physically plugged in.
The attach does not persist across reboots. Automate with a scheduled task:
# Replace IP with Tailscale IP (100.x.x.x) if using Tailscale
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -WindowStyle Hidden -Command `"usbip attach -r 100.x.x.x -b 2-9`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "USB-IP Auto Attach" `
-Action $action -Trigger $trigger -Principal $principalThe tunnel also needs to be re-established after reboot. Use the scheduled task from the SSH tunnel section above, then add a second task for the attach that runs a few seconds later:
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -WindowStyle Hidden -Command `"Start-Sleep 5; usbip attach -r localhost -b 2-9`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "USB-IP Auto Attach (SSH)" `
-Action $action -Trigger $trigger -Principal $principalClose and reopen PowerShell. If still not found:
$env:PATH += ";C:\Program Files\usbipd-win"Try unbinding and rebinding:
usbipd unbind --busid 2-9
usbipd bind --busid 2-9
usbipd list- Check if GPO is blocking local rules:
netsh advfirewall show allprofiles - If
LocalFirewallRules: N/A (GPO-store only)appears, local rules are ignored — use Tailscale or the SSH tunnel. - Simplest fix: install Tailscale on both machines and use the Tailscale IP.
- Confirm both machines are in your tailnet: https://login.tailscale.com/admin/machines
- Confirm the client device has been approved if device approval is enabled
- Try
ping 100.x.x.xfrom the client to the host's Tailscale IP - Run
tailscale statuson both machines to confirm they show as connected
- Check the key has not expired (set expiry when generating)
- Check the key has not already been used (one-time keys expire after first use)
- Generate a new key from the admin panel
- Confirm the usbipd service is running on the host:
Get-Service -Name usbipd Start-Service -Name usbipd # if stopped
- Confirm the device is still in Shared state:
usbipd list
- Confirm the reboot after usbip-win2 installation was completed
- Try detaching and reattaching:
usbip detach -p 00 usbip attach -r <HOST-IP> -b <BUSID>
- Check Windows Event Viewer under Windows Logs → System for driver errors
Some license systems fingerprint the USB connection and can detect virtualization. This is rare but possible. If it occurs, a hardware KVM switch or physical USB extender cable may be the only alternative.
Also ensure the device is not mounted on the host (e.g. as a drive letter) while the client is using it — eject it on the host before the client attaches.
Disable USB selective suspend on the host:
powercfg /setacvalueindex SCHEME_CURRENT 2a737441-1930-4402-8d77-b2bebba308a3 48e6b7a6-50f5-4782-a5d4-53bb8f07e226 0
powercfg /setactive SCHEME_CURRENTAlso ensure the host machine does not sleep while the client is using the device.
-
usbipd-win has no built-in authentication. Always restrict who can connect using one of these approaches:
- Tailscale with device approval and ACLs (most secure)
- Firewall rule scoped to the client's specific IP on a LAN
- SSH tunnel with key-based authentication
-
Tailscale authentication model:
- No passwords are stored on the client machine — Tailscale uses OAuth via your identity provider
- The node key issued to a machine is useless without the identity provider session behind it
- Use pre-auth keys with short expiry and one-time use for onboarding
- Enable device approval so no machine joins without your explicit sign-off
- Use ACLs to restrict the client to port 3240 on the host only
- Revoke access instantly from the admin panel at any time
-
SSH tunnel traffic is encrypted but requires SSH credentials — a good fallback when Tailscale is not available.
-
Do not expose port 3240 on a public or untrusted network without Tailscale or a VPN.
-
The usbipd service runs as SYSTEM. Keep the software updated.
# Install
winget install usbipd
# List devices
usbipd list
# Bind (share) a device — run once, persists across reboots
usbipd bind --busid <BUSID>
# Unbind (stop sharing)
usbipd unbind --busid <BUSID>
# Open firewall — LAN (all profiles)
New-NetFirewallRule -DisplayName "usbipd USB/IP" -Direction Inbound `
-Protocol TCP -LocalPort 3240 -Action Allow -Profile Domain,Private,Public
# Open firewall — Tailscale interface only (recommended)
New-NetFirewallRule -DisplayName "usbipd USB/IP (Tailscale)" -Direction Inbound `
-Protocol TCP -LocalPort 3240 -Action Allow -InterfaceAlias "Tailscale"
# Check if GPO is overriding local firewall rules
netsh advfirewall show allprofiles
# Get Tailscale IP
tailscale ip
# Check Tailscale connection status
tailscale status
# Check usbipd service status
Get-Service -Name usbipd
# Enable OpenSSH Server (SSH tunnel fallback)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic# Install: download from https://github.com/vadimgrn/usbip-win2/releases
# Reboot after install
# Join tailnet using pre-auth key (no credentials needed)
tailscale up --authkey=<KEY-FROM-ADMIN-PANEL>
# Test connectivity
Test-NetConnection -ComputerName <HOST-IP> -Port 3240 # LAN
Test-NetConnection -ComputerName 100.x.x.x -Port 3240 # Tailscale
# Attach device (direct or Tailscale)
usbip attach -r <HOST-IP> -b <BUSID>
usbip attach -r 100.x.x.x -b <BUSID> # Tailscale
# SSH tunnel (if port 3240 is blocked and no Tailscale)
ssh -L 3240:localhost:3240 <USERNAME>@<HOST-IP> # keep open
usbip attach -r localhost -b <BUSID> # second window
# List attached ports
usbip port
# Detach device
usbip detach -p <PORT>| Task | URL |
|---|---|
| View all machines | https://login.tailscale.com/admin/machines |
| Generate pre-auth key | https://login.tailscale.com/admin/settings/keys |
| Edit ACL policy | https://login.tailscale.com/admin/acls |
| Device approval settings | https://login.tailscale.com/admin/settings/devices |