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?
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.
2 days ago
But it doesnt work...
Status changed to Awaiting Railway Response Railway • 2 days ago
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
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.
2 days ago
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.
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:
-
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.
-
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.
-
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.
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-runIf the generated configuration looks correct, apply it:
railway ssh config --service MySQL --environment production --alias railway-mysqlReplace 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-mysqlThis 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:
- Railway SSH documentation: https://docs.railway.com/cli/ssh
- Railway Connect documentation: https://docs.railway.com/cli/connect
- Similar solved issue: https://station.railway.com/questions/railway-cli-tunnel-only-flag-not-working-e673de90
One additional suggestion: Railway should improve the error handling so URL parsing failures are reported separately from actual TCP Proxy validation failures.