Debugging a bug that only happens on a machine without Visual Studio
A bug reproduces on the test server and not on your laptop. The test server has no Visual Studio, and installing a full IDE there to set one breakpoint isn’t an option. Remote debugging is built for this case. A small standalone program runs on the target machine, your local Visual Studio connects to it over the network, and you get breakpoints and variable inspection on a process that isn’t running anywhere near your desk.
There are three steps: run the remote debugger on the target, attach from Visual Studio, load matching symbols. Each one fails in its own specific way. After those come the two remote targets you’re most likely to hit in practice, a process inside a Docker container and an app on Azure App Service.
How to install and run the Visual Studio remote debugger (msvsmon)
The program is msvsmon.exe, the Visual Studio Remote Debugger. You can get
it onto the target machine in two ways.
If the target already has Visual Studio Community, Professional, or
Enterprise installed, msvsmon.exe is already there, under
<Visual Studio install folder>\Common7\IDE\Remote Debugger\x64\ (or
x86\, matching the architecture you’re debugging). Run it directly.
Otherwise, download the standalone remote tools package from Microsoft’s remote-tools page for your Visual Studio version, and take the latest update of it. The compatibility only runs one way: newer remote tools work with an older Visual Studio, but older remote tools can refuse a newer one. Pick the architecture of the target OS (x86, x64, or ARM64), not of the app you’re debugging.
The first launch opens a configuration wizard that adds a Windows Firewall rule for the network type you’re on (domain, private, or public). When it finishes, the Remote Debugger window shows a server name and port. That’s the address you’ll type into Visual Studio. Leave the window open; closing it (File > Exit) ends the session.
Remote debugger ports and authentication: what blocks the connection
The wizard’s firewall rule only opens the inbound TCP port, and that port changes with the Visual Studio version. It’s 4026 for Visual Studio 2022; older releases use different numbers. If the target sits behind a firewall the wizard didn’t configure, such as a corporate appliance or a cloud security group, open that specific port yourself. Guessing “the debugger port” without checking your version wastes time.
UDP 3702 outbound is optional. It lets the target show up in the Find picker in Visual Studio. Without it you can still connect; you type the address by hand.
Authentication mode has to match on both ends. By default the remote
debugger uses Windows Authentication, so the Windows account running Visual
Studio needs permission on the remote debugger. Grant it from Tools >
Permissions in the Remote Debugger window (administrator rights required),
or start msvsmon from the command line with the account already allowed:
| |
Switching to No Authentication (Tools > Options in the Remote Debugger window) skips all of that, and on a throwaway VM it’s tempting. Do it only on a network you trust completely. Anyone who can reach that port can attach to any process running under the same account: no prompt, no password.
How to attach to a remote process from Visual Studio
With the remote debugger running and reachable:
- In Visual Studio, open Debug > Attach to Process (
Ctrl+Alt+P). - Set Connection type to Remote (Windows).
- In Connection target, type the address in the format the remote
debugger’s own window shows, usually
<machine-name>:<port>. If the machine name doesn’t resolve, use the IP:10.0.0.15:4026. - Click Refresh to pull the process list from the remote machine, then find and select your process. If it isn’t running under your account, check Show processes from all users. Visual Studio shows a security warning before you attach to a process owned by someone else; read it rather than dismissing it.
- Leave Code type on Automatic. The exception is a mixed-code process where attaching fails silently for one language: pick that language explicitly and reattach, and you get a specific error instead of nothing.
- Click Attach.
For an IIS-hosted ASP.NET app, the process you want is w3wp.exe. For a
.NET app running its own Kestrel server, it’s dotnet.exe or the app’s own
executable name. IIS often runs several identical w3wp.exe processes side
by side. The Command Line column in the process list is what tells them
apart.
Fixing “no symbols loaded” when remote debugging
Attaching doesn’t mean you can debug yet. Open Debug > Windows > Modules and look at the Symbol Status column for your binary. A hollow red breakpoint circle instead of a solid one says the same thing from the editor side: the debugger has no symbols to map that breakpoint to.
Three statuses show up in practice:
| Status | What it means | Fix |
|---|---|---|
Skipped loading symbols | Symbol loading is filtered out by your settings | Check Debug > Options > Symbols for an include/exclude rule blocking this module |
Cannot find or open the PDB file | No matching .pdb is reachable | Copy the exact .pdb from the build that produced the deployed binary to the remote machine, or point Visual Studio at it |
| No entry / native-looking hex only | The module has no debug info at all | It was built in Release without a .pdb, or it’s a system DLL you don’t need to debug |
Most of the wasted time here comes from one rule: the debugger only
accepts a .pdb that exactly matches the binary. Not “the same version”.
The file from that specific build. If the remote machine was redeployed
after your last local build, your local .pdb no longer matches. When
automatic loading doesn’t find it, right-click the module in the Modules
window, choose Load Symbols, and browse to the deployment’s own output
folder.
The habit that prevents all of this: ship the .pdb alongside the binary on
every deploy you might need to debug. Reconstructing a matching one after
the fact rarely works.
How to debug a process in a Docker container
Everything above assumes a Windows machine you can install software on. A
container is different. You don’t put msvsmon in a production image, and
depending on the
Docker network
setup, the container may not even be reachable from your host by name.
If the project has Docker support enabled (Add > Docker Support, or a
project already scaffolded with a Dockerfile), Visual Studio’s container
tooling does the work: F5 builds a debug-configured image, starts the
container, and attaches. For a container you didn’t start from Visual
Studio, open Debug > Attach to Process, set Connection type to
Docker (Linux Container) or Docker (Windows Container) to match the
image, and pick the container from the list. Visual Studio goes through the
Docker daemon and puts its own debugger into the running container, so you
never add one to the image yourself.
How to remote debug an app on Azure App Service
For an app deployed to Azure App Service, skip the manual remote-debugger setup. Right-click the project in Solution Explorer, choose Publish, then from the publish profile’s … menu select Attach Debugger. Visual Studio enables the remote debugging endpoint on the App Service instance and handles the connection and authentication for you. Once you’re attached, the symbol rules from the previous sections still apply. Check the Modules window before trusting a breakpoint.
Remote debugger or git bisect: which one to use first
A remote debugger tells you what a specific line is doing right now, on a
specific machine. It can’t tell you which commit introduced the problem.
git bisect answers that one,
with a binary search over history instead of a search through your memory.
For a regression, run them in that order. If you can’t yet say which commit broke things, bisect first. Once you have the commit and a machine where the bug reproduces, set up the remote debugger there and find out why.