Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
CLI · sf

ERROR running force:auth:web:login: Invalid login URL

The Salesforce CLI couldn't authenticate against the URL you gave it. Either the URL is wrong (typo, missing https, hitting a non-Salesforce host), the org has My Domain enforced and you're trying to use the generic login URL, or your Connected App config is rejecting the device-code/browser flow.

Also seen asInvalid login URL·ERROR running force:auth:web:login·INVALID_LOGIN_URL·sf cli authentication failed

You're trying to authorize a sandbox from your laptop before a release deploy. The command runs, a browser tab opens, you log in, the browser says success, and then the terminal prints ERROR running force:auth:web:login: Invalid login URL. The deploy window opens in twenty minutes. You haven't even reached the metadata push step yet.

What the CLI is actually checking

When you authorize an org through the Salesforce CLI, the tool opens a browser to the login URL you provided (or to the default, https://login.salesforce.com), waits for an OAuth callback on a local port, and exchanges the returned code for an access token plus a refresh token. The Invalid login URL error fires when the value passed to --instance-url (or stored in your alias, or read from your sfdx-project.json) doesn't match what the OAuth handshake expects.

The URL the CLI passes is used in two ways. It selects which Salesforce edge to authenticate against, and it's embedded in the OAuth state so the callback knows which org issued the token. If the host string isn't a valid Salesforce login or My Domain URL, the handshake bounces.

The error is upstream of the username and password. You can have perfectly valid credentials and still hit this. The CLI never gets far enough to send your credentials anywhere.

The broken example

A common shape, copied from a runbook somewhere:

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

The URL https://acme.my.salesforce.com looks correct. It's a valid My Domain. But it's the runtime URL, not a login URL. The CLI doesn't accept arbitrary My Domain runtime URLs as the OAuth origin. The handshake fails with Invalid login URL.

Another common shape that breaks for a different reason:

sf org login web \
  --alias acme-sandbox \
  --instance-url https://test.salesforce.com/services/oauth2/token

The user pasted the OAuth token endpoint instead of the host. The CLI wants the bare host (https://test.salesforce.com), not a path under it. The trailing path corrupts the OAuth state.

A third shape, slightly more obscure:

sf org login web \
  --alias acme-prod \
  --instance-url login.salesforce.com

The scheme is missing. The CLI requires https:// on every URL it parses. A bare hostname looks like a relative URL and gets rejected.

Three paths to a fix

The likeliest cause, in order:

Wrong URL kind: My Domain runtime vs login. Use https://login.salesforce.com for production orgs and https://test.salesforce.com for sandboxes. If your org enforces My Domain login, use https://YOURDOMAIN.my.salesforce.com for production or https://YOURDOMAIN--SANDBOXNAME.sandbox.my.salesforce.com for the sandbox flavor. The pattern matters; a generic https://acme.my.salesforce.com without the sandbox segment fails for sandbox orgs and may also fail for production if the My Domain hasn't been deployed.

Path appended to the URL. Strip everything after the host. The CLI parses out the path and rejects it. Paste only the scheme and host: https://login.salesforce.com, never https://login.salesforce.com/setup/... or https://login.salesforce.com/services/....

Missing scheme. Always include https://. The CLI will not infer the scheme for you.

Run the command again with the corrected URL and the browser tab should complete the handshake.

The fixed example

For a production org with the default login endpoint:

sf org login web \
  --alias acme-prod \
  --instance-url https://login.salesforce.com

For a sandbox using the test endpoint:

sf org login web \
  --alias acme-uat \
  --instance-url https://test.salesforce.com

For an org that enforces My Domain login on production:

sf org login web \
  --alias acme-prod \
  --instance-url https://acme.my.salesforce.com

For the same org's UAT sandbox with My Domain enabled:

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

The CLI opens the browser, you complete the login, the callback fires, and the alias gets stored in ~/.sfdx/.

When the URL is right but it still fails

Three less common causes, in decreasing frequency.

Local port conflict. The OAuth callback listens on a localhost port (default 1717, configurable with --callback-url). If another process owns that port, the callback never arrives and the CLI eventually times out. Symptoms: the browser shows success, the terminal sits for a long time, then prints a confusing error. Pass --callback-url http://localhost:1718/OauthRedirect or stop the offending process to free the default port.

Proxy or VPN interception. Corporate proxies that intercept HTTPS traffic can rewrite or block the OAuth response. The browser tab completes; the local callback never sees the code. Set the HTTP_PROXY and HTTPS_PROXY environment variables to match your proxy configuration, or temporarily disable the proxy for localhost.

Expired CLI version. Older versions of the CLI predate certain My Domain URL formats. If your org switched to enhanced domains in a recent release, an outdated CLI may not recognize the new URL shape. Run sf update and try again.

The JWT and client-credentials alternatives

For CI pipelines and headless servers, the interactive browser flow isn't available. Two alternatives cover this case.

The JWT bearer flow uses a server certificate to obtain a token without a browser. Generate a key pair, configure a Connected App with the public key, and authenticate with:

sf org login jwt \
  --alias acme-ci \
  --username deploybot@acme.com \
  --client-id 3MVG9...your_consumer_key \
  --jwt-key-file server.key \
  --instance-url https://login.salesforce.com

The client-credentials flow uses a client id and secret stored as repo secrets:

sf org login client-credentials \
  --alias acme-ci \
  --client-id 3MVG9... \
  --client-secret YOUR_SECRET \
  --instance-url https://acme.my.salesforce.com

Both flows still validate the instance URL. A wrong URL produces the same Invalid login URL error. The fix is the same: strip paths, include the scheme, use the correct login host for the org type.

When the alias points to the wrong URL

If you've previously authorized an org under an alias and the URL changed (My Domain rolled out, or the sandbox refreshed), the alias may still hold the old URL. Subsequent commands using --target-org acme-uat can fail with the same error even though you didn't pass --instance-url.

Inspect the stored alias:

sf org display --target-org acme-uat

Look at the Instance Url value. If it's stale, delete the alias and re-authorize:

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

When sfdx-project.json overrides your flag

The sfdx-project.json at the repo root can declare a sfdcLoginUrl field that sets the default login URL for the project. A value here applies to every org login command run from the directory unless the CLI flag explicitly overrides it.

{
  "packageDirectories": [{ "path": "force-app", "default": true }],
  "namespace": "",
  "sfdcLoginUrl": "https://acme.my.salesforce.com",
  "sourceApiVersion": "60.0"
}

If the project file's value is wrong, fix it in the file. CLI flags take precedence, so passing --instance-url always wins, but the project file affects defaults across the team and CI.

Sandbox URL patterns to memorize

Sandboxes follow a strict pattern that trips people up. For an org with My Domain acme and a sandbox named uat, the URLs are:

  • Production: https://acme.my.salesforce.com
  • UAT sandbox: https://acme--uat.sandbox.my.salesforce.com
  • Full copy named staging: https://acme--staging.sandbox.my.salesforce.com

The double dash separates the My Domain name from the sandbox name. The .sandbox. segment is what tells the platform this is a sandbox edge. Generic https://test.salesforce.com works for sandboxes too, but enhanced-domain orgs may require the specific My Domain shape.

When in doubt, log into the org through a browser, copy the URL from the address bar after the page loads, strip the trailing /lightning/... path, and use that as the instance URL. The host portion is what the CLI accepts.

Diagnosing a stuck callback

If the browser shows success and the terminal hangs, run the CLI with verbose logging:

SF_LOG_LEVEL=trace sf org login web --alias debug --instance-url https://login.salesforce.com

The trace output shows the callback URL being polled, the port being listened on, and any HTTP errors during the exchange. A repeated "port not open" or "connection refused" message points to a port conflict. A long pause followed by "Error parsing token response" points to a proxy intercepting traffic.

Test that your auth is working

After a successful login, the simplest smoke test is a metadata describe:

sf org list metadata --target-org acme-uat --metadata-type CustomObject

If this returns a list, auth is working. If it returns INVALID_SESSION_ID, the token isn't being used. If it returns the original Invalid login URL, the alias didn't store the right URL and you should re-authorize.

Related errors to recognize

The Invalid login URL error sometimes precedes or follows these neighbors:

  • INVALID_LOGIN: Invalid username, password, security token; or user locked out (after the URL is correct, before credentials succeed)
  • INVALID_GRANT: authentication failure (OAuth refresh failed; redo full auth)
  • error_description: invalid_client_id (your Connected App consumer key is wrong)
  • INVALID_SESSION_ID: Session expired or invalid (auth succeeded earlier, token aged out)

Each has a distinct remediation. The login URL family is specifically about the host string. The session-id family is about token lifetime. The grant family is about the OAuth handshake itself.

When the alias works locally but breaks in CI

A common gotcha: you authenticate from your laptop and everything works, but the same alias used in a CI pipeline fails. The CI runner uses a different home directory, so the credentials stored under your local ~/.sfdx aren't available. The CI needs its own auth.

For GitHub Actions, the standard pattern is to use sf org login jwt at the start of the workflow with secrets stored as repository secrets. The key file is decrypted from a base64-encoded secret, the login runs against a fixed alias, and subsequent steps use that alias.

- name: Authenticate to Salesforce
  run: |
    echo "${{ secrets.SF_JWT_KEY }}" | base64 -d > server.key
    sf org login jwt \
      --alias prod \
      --username ${{ secrets.SF_USERNAME }} \
      --client-id ${{ secrets.SF_CLIENT_ID }} \
      --jwt-key-file server.key \
      --instance-url https://login.salesforce.com

The first time you set this up, expect to spend an hour matching the right secrets to the right Connected App. Once it works, it works reliably.

Habits that prevent this

Three small habits save real time across a team.

Store the instance URL alongside the alias in a project README so new developers don't paste the wrong value. Document the production URL, every sandbox URL, and any scratch-org or dev-hub URLs. Keep the document version-controlled so when a sandbox URL changes (after a refresh or domain rename), the update flows to everyone on the next pull.

Keep the CLI current. Run sf update weekly or pin a known-good version in your CI image. Most "the CLI suddenly stopped working" tickets resolve themselves on a fresh install. The Salesforce CLI changes its behavior across releases more often than it changes the underlying APIs, so an old CLI talking to a current org is a common cause of confusing failures.

For CI, prefer JWT or client-credentials authentication over interactive flows. The interactive flow can't run in a headless container, and trying to make it work leads to weird timeout failures that look like URL bugs but are really about port forwarding inside the container. JWT-based auth has been the standard pattern for several years and works reliably once configured. The setup overhead pays back the first time CI runs unattended overnight.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Salesforce errors