railway connect --ssh fails with "Connection URL should point to the Railway TCP proxy" on MySQL
nonamehansen
PROOP

2 days ago

CLI version: 5.71.0 (macOS)

What I'm trying to do

Open an SSH tunnel to a private MySQL database (no TCP proxy / public domain enabled) so I can run read-only queries locally.

Commands tried

railway connect --environment production --tunnel-only --port 33061

railway connect --environment production --ssh --tunnel-only --port 33061

railway connect --project --environment production --ssh --tunnel-only --port 33061

The last one was run from a directory with no linked project, to rule out local develop overrides.

Result (all three)

Connection URL should point to the Railway TCP proxy

Exit code 1.

Expected

According to railway connect --help, --ssh "works without a public domain; auto-enabled when the service has no public proxy URL", and --tunnel-only implies --ssh. I expected a

local tunnel and printed connection details.

Context

  • The service is a standard Railway MySQL template service.
  • MYSQL_PUBLIC_URL and the RAILWAY_TCP_PROXY_* variables are not set on the service, so there is no public proxy. That is the case --ssh is meant to handle.
  • railway run against the same service works and injects the private variables.
  • In a project folder with a docker-compose.yml, railway run prints "Using local develop services". connect has no --no-local flag, but the error is the same outside that folder.

It looks like connect validates the proxy URL before it falls back to SSH. Is this a known issue, or is something extra needed on the service for SSH tunnelling?

Awaiting Conductor Response$60 Bounty

6 Replies

Status changed to Awaiting Railway Response Railway • 2 days ago


2 days ago

Nothing extra is needed on the service for SSH tunnelling. railway connect is designed so that when a database has no public TCP Proxy, --ssh and --tunnel-only open a local SSH tunnel and print the connection details, with no public URL required.

The setup you described (no MYSQL_PUBLIC_URL or TCP proxy variables, --tunnel-only with a fixed local port) is exactly that case, so there is no additional configuration on your side to add.


Status changed to Awaiting User Response Railway • 2 days ago


brody

Nothing extra is needed on the service for SSH tunnelling. `railway connect` is designed so that when a database has no public TCP Proxy, `--ssh` and `--tunnel-only` open a local SSH tunnel and print the connection details, with no public URL required. The setup you described (no `MYSQL_PUBLIC_URL` or TCP proxy variables, `--tunnel-only` with a fixed local port) is exactly that case, so there is no additional configuration on your side to add.

nonamehansen
PROOP

2 days ago

But it doesnt work...


Status changed to Awaiting Railway Response Railway • 2 days ago


Railway
BOT

2 days ago

This thread has been opened as a bounty so the community can help solve it.

Status changed to Open Railway • 2 days ago


halfabillion
HOBBY

2 days ago

Part 1: Why you get this error even with --tunnel-only

The message is misleading. In CLI 5.71.0, --tunnel-only goes straight to the SSH tunnel code and never checks for a TCP proxy. On that path the CLI reads MYSQL_URL (the private URL), parses it, and swaps the host for 127.0.0.1. If MYSQL_URL can't be parsed as a URL, the CLI returns its generic InvalidConnectionVariable error, and that error's text is "Connection URL should point to the Railway TCP proxy". So the real problem is almost certainly the value of MYSQL_URL on the MySQL service, not SSH.

I checked these values against the same Rust url crate the CLI uses:

• mysql://root:[email protected]:3306/railway parses fine

• a password containing / or ? or # (not percent-encoded) fails with "invalid port number"

• an empty host, for example mysql://root:PASSWORD@:3306/railway, fails with "empty host"

• an empty MYSQL_URL fails too

If MYSQL_URL were missing completely you'd see a different error ("Could not find a variable to connect to the service with"), so it exists but is malformed.

Part 2: Check the value

Step a: Open the MySQL service, go to Variables, and look at MYSQL_URL (or run railway variables --service MYSQL_SERVICE --environment production --kv privately, since it prints secrets).

Step b: Check that the host part after the @ is not empty, for example mysql.railway.internal.

Step c: Check whether the password contains / ? # or @. These have to be percent-encoded inside a URL.

Part 3: Fix

Step a: If the password has special characters, either percent-encode them inside MYSQL_URL (/ becomes %2F, ? becomes %3F, # becomes %23, @ becomes %40), or rotate the MySQL password to letters and digits only and update MYSQLPASSWORD, MYSQL_ROOT_PASSWORD and MYSQL_URL together.

Step b: If the host is empty, set MYSQL_URL back to the template form so it references the private domain: mysql://${{MYSQLUSER}}:${{MYSQL_ROOT_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:3306/${{MYSQL_DATABASE}}

Step c: Deploy the variable change, then run again: railway connect MYSQL_SERVICE --environment production --tunnel-only --port 33061

You should then get the "tunnel open" block with Host 127.0.0.1, Port 33061, user, password and database.


We had a similar issue a while back, and the solution was to delete the generated ssh keys: id_ed25519, and id_ed25519.pub, in the folder .ssh, then run the command ssh-keygen -t ed25519, then run the connect command again.


nonamehansen
PROOP

2 days ago

Thanks @halfabillion, you pointed me to the right place. The error comes from MYSQL_URL failing to parse on the --tunnel-only path. It has nothing to do with SSH or the TCP proxy.

In my case the cause was different from the ones you listed. MYSQL_URL on the MySQL service had been overwritten with a JDBC URL that had no credentials:

jdbc:mysql://mysql-xxxx.railway.internal:3306/railway

My Spring Boot service references ${{MySQL.MYSQL_URL}} and expects JDBC format, so at some point the value on the MySQL service was changed to make the app work. With the jdbc:

prefix, the url crate parses jdbc as the scheme and finds no host. The CLI then returns InvalidConnectionVariable, whose message is the misleading "Connection URL should point to

the Railway TCP proxy".

Fix:

  1. Give the app service its own variable instead of referencing the database's:

    MYSQL_URL=jdbc:mysql://${{MySQL.RAILWAY_PRIVATE_DOMAIN}}:3306/${{MySQL.MYSQL_DATABASE}}

    Redeploy the app and confirm it starts.

  2. Restore the template value on the MySQL service:

    MYSQL_URL=mysql://${{MYSQLUSER}}:${{MYSQL_ROOT_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:3306/${{MYSQL_DATABASE}}

    The database itself doesn't read this variable, so it doesn't need a redeploy.

  3. railway connect MySQL --environment production --tunnel-only --port 33061 now opens the tunnel and prints the connection details.

Two more things that might help others:

  • In a folder with a docker-compose.yml, railway run silently swaps in local develop values ("Using local develop services"). Use railway run --no-local to inspect the real

    variables.

  • It would help if the CLI reported the actual parse error, e.g. "MYSQL_URL could not be parsed as a URL", instead of the TCP proxy message.

Accepting @halfabillion's answer, since it led to the fix.


Anonymous
HOBBY

an hour ago

I believe this is likely a CLI URL-parsing/rewrite issue rather than a missing TCP Proxy configuration.

According to Railway's current CLI documentation, --tunnel-only explicitly implies --ssh and should establish a private SSH tunnel without requiring a public TCP Proxy.

I would recommend bypassing railway connect and creating the SSH tunnel directly.

Step 1 – Generate the SSH configuration:

railway ssh config --service MySQL --environment production --alias railway-mysql --dry-run

If the generated configuration looks correct, apply it:

railway ssh config --service MySQL --environment production --alias railway-mysql

Replace MySQL with the exact name of your Railway database service if different.

Step 2 – Establish a local SSH tunnel:

ssh -N -L 13306:127.0.0.1:3306 railway-mysql

This forwards your local port 13306 to MySQL's internal port 3306 through Railway SSH. No public TCP Proxy needs to be enabled.

Make sure your Railway SSH key is registered before connecting.

Step 3 – Connect using DBeaver, TablePlus, or MySQL Workbench:

Use the following connection settings:

  • Host: 127.0.0.1
  • Port: 13306
  • Database: MYSQLDATABASE
  • Username: MYSQLUSER
  • Password: MYSQLPASSWORD

Keep the SSH terminal running while using your database client.

Why I suspect a CLI issue:

There is already a solved Railway Central Station report involving the exact same error message. Investigation of that case found that the tunnel path could reuse the misleading TCP Proxy error when parsing or rewriting the database connection URL failed.

This suggests that the error message does not necessarily mean a public TCP Proxy is required.

For your MySQL case, I would specifically test whether the direct SSH tunnel succeeds. If it does, that would isolate the problem to the railway connect --tunnel-only code path rather than the database's private networking configuration.

References:

One additional suggestion: Railway should improve the error handling so URL parsing failures are reported separately from actual TCP Proxy validation failures.


Welcome!

Sign in to your Railway account to join the conversation.

Loading...