Skip to main content

Railway Has SSH Now. It's Still Not Your Server

8 min readDora NodaDora Noda
Share
On this page

Type railway ssh today and you land in a shell inside your deployed service. No sidecar, no SSH daemon baked into your image, no support ticket — just your system ssh client against ssh.railway.com, with scp, SFTP, port forwarding, and persistent tmux sessions thrown in. A decade after Heroku normalized remote shells on a PaaS, shell access is now a standard product feature on every general-purpose hosting platform.

But a feature is not a property. SSH on a hosted PaaS and SSH on a machine you own share a prompt and almost nothing else. This post answers one concrete question: what does PaaS shell access actually grant you, where does it stop, and when do you need root on your own hardware instead? The short version is the table below; the rest of the post substantiates every row.

PaaS SSH (railway ssh, Render, fly ssh console)Root SSH on a machine you own
What you land inAn exec session inside one ephemeral containerThe host itself, every container and process on it
PersistenceNothing survives a redeploy; filesystem is ephemeral outside mounted volumesThe disk is yours; changes persist until you revert them
AuthenticationVendor-mediated: a key registered with the platform, gated by their control planeYour keys, your sshd, your firewall rules
Network visibilityInside the container's network namespace onlyHost interfaces, packet capture, the full network path
Kernel accessWhatever the container runtime permits — no perf, no modules, no sysctlsFull: profilers, eBPF, kernel tunables, custom modules
When the workload is wedgedIf the container won't start, there is nothing to attach toRescue the host, inspect the dead container, read the node
AvailabilityDown when the vendor's control plane is downReachable whenever the machine has power and a route

What PaaS SSH actually grants you

The implementations have converged on remarkably similar shapes, so it is worth being concrete about what each one is.

Railway routes your system ssh client to ssh.railway.com, authenticated with a key you register to your account. From there you get an interactive shell in the service container, one-shot remote commands (railway ssh -- ls -la), persistent tmux sessions that survive a dropped connection, scp/SFTP file transfer against the running container, and local port forwarding. Notably, your container needs no SSH daemon — the platform injects the session from outside. The SSH username is the service's domain, or its instance ID for services without one.

Render has offered the same shape for years: ssh srv-abc123@ssh.oregon.render.com, where the username names the service and the hostname names the region. Two limits matter: with multiple instances you land on one at random, and you cannot SSH into an instance that is no longer running.

Fly.io gets you there over a different transport — fly ssh console dials your VM over a WireGuard peer the CLI sets up for you — but the destination is the same kind of place: inside your workload, not underneath it.

Heroku defined the lineage. heroku run bash spins up a brand-new one-off dyno with your code on it — a fresh copy, not your running process. heroku ps:exec is the closer ancestor of modern PaaS SSH: an SSH tunnel into the running dyno. And its documented limits read like a prophecy of every row in the table above: each connection lasts at most an hour, it is unavailable for one-off and release-phase dynos, and the session does not even carry your config vars.

A decade of convergence — including Railway's own reluctance

The arc runs from Heroku's one-off dynos, through ps:exec's tunnel-into-the-running-process, through Render's per-service SSH users and Fly's WireGuard console, to Railway's mid-2026 arrival. Every vendor independently landed on the same design: authenticate against our control plane, attach to the workload container, change nothing about the host.

Railway's own history makes the point nicely. Its feature-request tracker carried an SSH-access request for years, and the notable thing in the thread is that even Railway staff hesitated: they worried about users modifying an instance and having their changes silently wiped on the next redeploy. They shipped it anyway — because for the common case they were right to. Languages with a REPL-driven debugging culture (Ruby, Elixir) genuinely need a live process to attach to, and telling users to instrument-then-redeploy for every inspection is a real productivity tax. The hesitation is worth remembering, though, because it names the exact boundary: the vendor knows the session is cosmetic to the deployment, and anything you do in it is a sandcastle below the redeploy tide line.

The four workflows that only exist on a machine you own

Here is the concrete deliverable the title promised: four debugging workflows that PaaS SSH cannot reach, each with the reason why.

1. Live kernel-level profiling. When latency spikes and application metrics look clean, the next step down is perf, eBPF profilers, and scheduler-level flame graphs — tools that need kernel privileges the container runtime will never grant your exec session. A PaaS SSH session shows you the process; it cannot show you what the kernel is doing to the process.

2. Packet capture on the real network path. tcpdump inside a PaaS container sees the container's virtual interface, after the platform's proxies, load balancers, and overlays have already shaped the traffic. Diagnosing cross-node retransmits, MTU black holes, or a misbehaving service mesh sidecar requires capturing on the host interfaces — which are outside every namespace your session can enter.

3. Disk forensics on the node. A volume that fills, corrupts, or slows down is a host-storage question: filesystem journals, device latency, inode exhaustion, a noisy neighbor on shared block storage. Your exec session sees a mount point. The evidence lives a layer below the mount, on a disk you cannot address.

4. Inspecting a wedged runtime. This is the sharpest edge. Every PaaS SSH implementation attaches to a running workload — Render documents it explicitly: dead instances are unreachable. When the container crash-loops, OOMs before init finishes, or the node itself is sick, there is no session to open. On your own machine, the failure is the beginning of the session, not the end of it: you read the dead container's filesystem, pull the kubelet and kernel logs from the host, and reboot or reimage the node yourself.

A fifth row deserves an honorable mention: outage independence. Vendor-mediated auth means your emergency access depends on the vendor's control plane being healthy at the exact moment you most need it. Machine SSH depends on the machine having power and a route — a failure domain you can reason about and redundantly provision.

When PaaS SSH is the right default

None of this is an argument against PaaS SSH. Most debugging sessions never leave the container, and for those, a vendor-provided shell is strictly better than the alternative — which, before it existed, was adding logging and redeploying just to inspect state.

The app-level list is long and legitimate: opening a Rails console or Elixir remote shell against production state, running a one-off data migration, checking which config vars actually resolved inside the process (Heroku's missing-vars gotcha aside), tailing a log the platform didn't forward, copying a file out of a crashed-but-still-running container for analysis. If your team's debugging life is this list, PaaS SSH is not a compromise — it is the whole answer, and buying a fleet of machines to improve on it would be pure overhead.

The honest reading, and the decision rule: if your debugging stays inside the container, PaaS SSH suffices; the day you need the host, you need the machine. Teams usually discover which side they are on at 3 AM, during the incident where the container won't start and the dashboard offers a shell button that opens onto nothing. That incident is the forcing function — before it, the PaaS shell is convenience; after it, host access is a requirement, and requirements get provisioned, not wished for.

One caveat for the SSH-maximalists: the mature endpoint of this arc may not be more SSH. Talos Linux — the immutable, API-driven node OS — deleted SSH entirely on the theory that hand-editing machines is the disease, not the cure. The direction of travel is debuggability without mutability: observe everything, change the declaration, let the machine reconcile.

The seam is the product

Strip away the branding and every PaaS SSH feature is the same sentence: we will let you look inside your workload, on our terms, while it runs. That sentence covers an enormous amount of real debugging. It stops exactly where the machine starts — and owning the machine, whether it is one Hetzner box or a Cluster API fleet of them, is the only thing that has ever extended the sentence further.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex