Salesforce CLI login failures get reported as one problem and are usually three. The CLI process runs somewhere, the browser that completes the web flow runs somewhere, and the later API call runs somewhere, and those three places do not have to share a route, a resolver or an allowlist. Comparing them is the fastest way to find out which one the tunnel is actually blocking. Nothing in the login output names which of the three failed.

Three hosts, one login story

The web login flow hands the identity step to a browser, and that browser is often on your laptop while the CLI sits on a remote development host or a container. The laptop has the VPN client, the remote host has whatever the platform team gave it, and neither arrangement automatically covers the other. A VPN icon in the status bar says nothing about a host that never ran the client.

After login, the CLI makes its own API requests from wherever the process lives. Those requests need DNS, routing, TLS trust and org-side client authorization, which are four separate checks that can each fail on their own. A browser that signed in beautifully tells you nothing about any of them.

Keep both halves in one timeline, but score them separately. A single ticket can then say which half broke without turning it into an argument about ownership.

Take the org entry from the org, not from memory

The web flow takes an instance URL and stores the result under an alias, and both values should come from the org owner rather than from a colleague's terminal history. A sandbox host copied from an old ticket is a common cause of a login that authenticates against the wrong org and then fails every query.

Use the shape the installed plugin documents, with your own approved host and alias. That documentation is the reference for flags that changed between sfdx and the current CLI.

sf org login web --instance-url https://acme--uat.sandbox.my.salesforce.com --alias uat
sf org display --target-org uat

The hostname and alias above are examples, not defaults you should copy. Complete the normal browser identity and MFA steps rather than scripting around them, and never place a password, session identifier or authenticator seed in a shell command to skip the prompt.

What the cached authorization proves

An alias that appears in the local auth list proves that a login once succeeded on that machine. The list is read from local storage and does not contact the org to confirm that the token still works, so a healthy-looking entry and a rejected API call can both be true. Treat the cache as a record of the past, not as a live connectivity test. A cache check answers a support question, but it never closes a connectivity investigation.

The same distinction applies to the browser session. A tab that still shows the org does not mean the CLI's stored token matches the current policy, because token lifetime, IP restrictions and client-app rules are enforced at the API boundary.

Read the local record before you delete it, since the alias and the org host are the two facts support will ask for. Removing an alias is reversible, but removing the wrong one in the middle of an incident just adds a second problem.

Where the API request is actually rejected

Follow one request end to end and note which component answers. A TLS failure names a different problem from a 403, which names a different problem from a timeout, and a timeout is the only one of the three that points at the tunnel as the first suspect. Reading the error class before the message saves a lot of guessing. Write the class down before you re-run anything.

Source-address restrictions are the case that produces a login that works and a query that does not, because the browser flow and the API call can leave through different addresses. Fixing that with a wider allowlist is an org-owner decision, not a convenience, and it should be the smallest range that covers the host.

Capture the failing command, the alias, the execution host and the timestamp, and leave the token out of whatever you paste into the ticket. That four-line summary is what the org owner needs in order to check their side.

Check the flow from the host that will use it

Test from the machine that will run the workload. A login verified on your laptop says nothing about a build agent in another network zone, and the difference is exactly what you are trying to find.

Use a harmless authorized read for the check, and keep the first error if it fails.

sf org list auth
sf org display --target-org uat
sf data query --target-org uat --query "select Id, Name from Organization"

When the login succeeds but the query fails, the remaining candidates are DNS, routing, TLS interception and org-side client policy, and each has its own owner. When the login itself fails, the browser and the CLI are probably not on the same host, and the fix belongs to the arrangement between them rather than to the tunnel. Automation gets one more rule: an unattended job needs an approved certificate-based login, because a cached interactive session copied to a worker is not an identity design.

Ebitabo ebifaananyi