Get started Dashboard
Observability ·
Part 19 of Platform playbooks

How to Search Railway Logs for Errors: Filters, CLI and HTTP Logs

Explore with AI

Open the Observability tab to use the Log Explorer, then filter with `@level:error` for application errors or `@httpStatus:500..599` for failed requests. From a terminal, run `railway logs --lines 100 --filter "@level:error"` for deploy logs, or `railway logs --http --status 500..599` for HTTP logs. The `@level` filter only works well if your app writes single-line JSON logs with a level field, because Railway marks all plain stdout as info and all stderr as error.

On this page

Railway captures everything your service writes to stdout and stderr and lets you search it with one filter syntax. You can search in three places: the deployment panel, the environment-wide Log Explorer and the railway logs CLI. The two queries that find most errors are @level:error in deploy logs and @httpStatus:>=500 in HTTP logs. Combine them with a time window and a service filter and you can usually go from “something is broken” to the failing line in a few minutes.

The catch is that @level:error is only as good as the level your logs carry. Railway infers a level for every line, and that inference can bury real errors or flood you with fake ones. So this page covers the queries, the CLI commands, and how to make your logs worth searching.

What an error means in Railway logs

In Railway, an error log is one of two things. In deploy logs, it is any line Railway has tagged with level error: either a line your app wrote to stderr, or a single-line JSON log with "level": "error". In HTTP logs, it is a request line whose @httpStatus is 400 or above, with 500 to 599 meaning your service failed. Searching for errors means filtering on those two attributes, then reading the lines around the match to find the cause.

The two kinds answer different questions. HTTP logs tell you which requests failed, on which path, and how long they took. Deploy logs tell you why, through the exception, the stack trace or the message your code printed.

Railway keeps several log types. Each answers a different question about an error:

  • Deploy logs: the stdout and stderr of your running app. Exceptions, stack traces and your own log lines live here.
  • Build logs: dependency installs and compile steps. Search these when a deploy never started.
  • HTTP logs: one line per request served through Railway’s edge, with method, path, status code and timing.
  • Environment logs: deploy logs from every service in an environment, searchable together in one place.
  • DNS logs: every DNS lookup a service makes, including lookups over the private network. Railway records them by default.
  • Network flow logs: connection-level traffic, including dropped packets. These are available from the CLI with --network.

You reach them in three ways:

  1. Build/Deploy panel: click a deployment in the service window. Open the Build Logs tab for build output.
  2. Log Explorer: click Observability in the top navigation. It shows logs for the whole environment, with a date range picker and column toggles.
  3. CLI: railway logs, which streams live by default and fetches history when you pass a time or line count.

The deployment panel suits a single failing deploy. The Log Explorer suits a problem that may span services, such as a database that every service depends on.

The filter syntax that finds errors

The same syntax works in the Log Explorer, the deployment panel and the CLI’s --filter flag.

error                       # partial substring match
"failed to send batch"      # exact phrase
@level:error                # attribute match
@arrayAttribute[0]:value    # array element
replica:<replica_id>        # one replica by UUID

Combine expressions with AND, OR and - for negation, and group them with parentheses.

Queries for application errors in deploy logs

@level:error
@level:error AND "ECONNREFUSED"
@level:error AND "failed to send batch"
@level:warn AND "rate limit"
@orderId:ord_123

Numeric comparisons also work on deploy logs when you log JSON. Railway supports >, >=, <, <= and .. for an inclusive range:

@task_duration:>=600
@batch_size:>100
@retries:1..3

Queries for failed requests in HTTP logs

HTTP logs carry their own attributes, so you can find errors without touching your code:

@httpStatus:500..599                         # server errors only
@httpStatus:>=400                            # all 4xx and 5xx
-@httpStatus:200                             # everything that was not a 200
@path:/api/checkout AND @httpStatus:>=500    # one endpoint
@totalDuration:>5000 @httpStatus:>=500       # slow and failed: the timeout pattern
@httpStatus:>=500 AND @edgeRegion:europe-west4-drams3a

Other HTTP attributes you can filter on include @method, @host, @requestId, @clientUa, @srcIp, @responseTime (time to first byte in milliseconds), @upstreamRqDuration, @txBytes and @rxBytes.

One attribute deserves special mention. @responseDetails is only populated when the application fails to respond. If you see it filled in, requests never reached a healthy process, and the next place to look is the deploy logs.

@responseDetails:"connection refused"

Queries across services in environment logs

Environment logs add a @service filter that takes a service UUID. Use it to cut noise from databases and caches:

@level:error AND -@service:<postgres_service_id>
@service:<postgres_service_id> OR @service:<redis_service_id>

Queries for DNS failures

When deploy logs show connection errors to another service, DNS logs tell you whether the name resolved at all. Open a deployment, click the Network tab and pick the DNS view.

@status:failed
@zone:internal AND @status:failed
@rcode:NXDOMAIN
@domain:api.stripe.com AND @status:failed

An NXDOMAIN on a .railway.internal name usually means the target service does not exist in this environment or the name is misspelled, which often happens after copying config between environments. TIMEOUT or SERVFAIL on an external name points at upstream DNS or the network.

Searching for errors from the terminal with railway logs

The CLI is the fastest route when you already have a terminal open, and it is the route a coding agent will take. With no flags, railway logs streams deploy logs from the most recent successful deployment over a WebSocket. Passing --lines, --since or --until switches it to fetch mode, which retrieves history.

Step by step: find the errors from the last hour

  1. Link the project, or pass --service and --environment explicitly:
railway logs --service backend --environment production --since 1h --filter "@level:error"
  1. If nothing comes back, check whether the latest deployment is the one failing. By default the CLI reads the most recent successful deployment, so a broken new deploy can be invisible:
railway logs --latest --lines 200 --filter "@level:error"
  1. Look at the requests that failed in the same window:
railway logs --http --status 500..599 --since 1h

Human-readable HTTP output shows the timestamp, method, path, status code, total duration in milliseconds and request ID for each request.

  1. Narrow to one endpoint or one request:
railway logs --http --method POST --path /api/users --status ">=400" --lines 50
railway logs --http --request-id abc123
  1. If the build is what broke, search the build output:
railway logs --build --filter "error"

Time windows

--since and --until take relative durations or timestamps:

railway logs --since 30m --until 10m --filter "@level:error"
railway logs --since 2024-01-15T10:00:00Z --filter "@level:error"

Fix the window once, at the start of an incident, and reuse it in every query. It stops you mixing up today’s error with last week’s.

Counting errors by path with JSON output

--json returns newline-delimited JSON. HTTP rows use camelCase fields such as path, httpStatus, totalDuration and requestId, so you can pipe them into jq to see which endpoints fail most:

railway logs --http --status 500..599 --since 1h --json \
  | jq -r '.path' | sort | uniq -c | sort -rn | head
    142 /api/checkout
     11 /api/users
      3 /healthz

The output above is illustrative. If one path dominates, suspect that handler or a dependency only it uses. If every path fails, the process itself is unhealthy: a crash loop, out of memory, or a database it cannot reach.

Typed flags and raw filters compose, so you can mix them:

railway logs --http --method GET --filter "@totalDuration:>=1000"
railway logs --http --filter "-@method:OPTIONS"

From an error report to the failing line

A search goes faster when you follow the same order every time. Start broad with HTTP logs, then drill into the deploy logs of the service that failed.

Steps from a Railway error report through HTTP, deploy and DNS logs to the failing line or a rollback

graph TB A[Users report errors] --> B[Fix the time window] B --> C[Query HTTP logs for 5xx] C --> D{responseDetails filled in?} D -->|yes| E[Process never answered: read deploy logs] D -->|no| F[Filter deploy logs with level error] E --> F F --> G{Connection errors?} G -->|yes| H[Check DNS logs for failed lookups] G -->|no| I[View in Context around the error] I --> J{Started at a deploy marker?} J -->|yes| K[Roll back, then read the commit] J -->|no| L[Fix the code path in the stack trace] style A fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d style K fill:#d1fae5,stroke:#6ee7b7,color:#065f46 style L fill:#d1fae5,stroke:#6ee7b7,color:#065f46

  1. Check the scope. In the Observability tab, confirm you are in the right environment. If several services error at once, suspect a shared dependency such as the database, a cache or an upstream API.
  2. Look for a change. Railway’s metrics graphs draw a dotted line at every deployment. A spike that starts at a dotted line is strong evidence the deploy caused it.
  3. Query HTTP logs for @httpStatus:500..599 in your window to see which paths fail.
  4. Search deploy logs with @level:error, then add a phrase once you see the shape of the failure.
  5. Read the surrounding lines. Right-click a log line and choose View in Context. Stack traces and the request that triggered them usually sit a few lines apart.
  6. Check for a crash loop. Railway restarts crashed deployments up to 10 times depending on your restart policy, then marks the deployment Crashed and notifies project members. Repeated startup errors in deploy logs show this pattern.
  7. Mitigate first. If a fresh deploy is the obvious cause, roll back from the deployment list. The rollback restores both the Docker image and the custom variables from that deployment, so it also reverts a bad variable change.

Making Railway logs searchable before the next error

Every query above leans on attributes. Plain text lines give you substring search and little else. Having built observability tools at Baselime and then at Cloudflare, the change I push hardest is this one: log JSON objects with fields, so an incident becomes a filter.

How Railway decides a line is an error

Railway normalises every incoming line so queries look the same across services:

  • Non-structured lines become {"message":"...","level":"..."}.
  • Lines from stdout get level info.
  • Lines from stderr get level error and show in red.
  • log.msg becomes log.message.
  • Levels are lowercased and matched to the closest of debug, info, warn and error.

Two consequences follow. A plain-text line printed to stdout that says ERROR: payment failed is stored as info, so @level:error misses it. And a logger that writes ordinary output to stderr turns every line into an error, so @level:error returns everything.

Emit single-line JSON with a level

console.log(
  JSON.stringify({
    message: "payment failed", // required
    level: "error",            // debug, info, warn or error
    orderId: "ord_123",        // query with @orderId:ord_123
    userId: 456,
  }),
);

The whole JSON object must sit on one line to be parsed. Libraries such as Winston for Node.js and structlog for Python emit this format for you. Structured logs also keep multi-line content like stack traces together, so put the stack in a field of the error line:

try {
  await chargeCard(order);
} catch (err) {
  console.log(JSON.stringify({
    level: "error",
    message: err.message,
    stack: err.stack,
    orderId: order.id,
  }));
  throw err;
}

Attach a request ID to every line

HTTP logs already carry @requestId, but your own lines need an ID too if you want one filter to return everything a request did. Generate one per request, add it to each log line and return it in a response header so users can quote it:

const { randomUUID } = require("node:crypto");

function withRequestId(handler) {
  return async (req, res) => {
    const requestId = randomUUID();
    res.setHeader("x-request-id", requestId);
    const log = (level, message, extra = {}) =>
      console.log(JSON.stringify({ level, message, requestId, ...extra }));
    try {
      await handler(req, res, log);
    } catch (err) {
      log("error", err.message, { stack: err.stack });
      res.statusCode = 500;
      res.end("internal error");
    }
  };
}

Then @requestId:3f8a2c1e-9b4d-4f6a-8c2d-1e5b7a9c3d2f returns every line that request produced. If you turn on tracing for the project, traced responses also carry an x-railway-trace-id header that points at the exact trace.

Traps that hide errors in Railway logs

  • The error is older than your retention. Railway keeps logs for 3 days on Free, 7 days on Trial and Hobby, 30 days on Pro and up to 90 days on Enterprise. Upgrading restores logs that had aged out. For longer history, forward stdout with Vector or Fluent Bit, or send logs from your app with a vendor SDK or OpenTelemetry. Railway has no log drain setting.
  • The rate limit dropped the lines you need. Railway enforces 500 log lines per second per replica on every plan and drops the excess. You will see a warning that starts Railway rate limit of 500 logs/sec reached for replica. An incident that triggers verbose error logging can hit this limit right when you need the logs. Log minified JSON, sample high-frequency events and turn down verbosity in production.
  • You searched the wrong deployment. railway logs defaults to the most recent successful deployment. Add --latest or pass a deployment ID when the newest deploy is the broken one. If the latest deployment failed, the CLI shows build logs by default.
  • Pretty-printed JSON. A JSON object spread over several lines is not parsed, so its fields cannot be queried. Emit it on one line.
  • Numeric filters on plain text. Comparisons like @task_duration:>=600 only work on deploy logs that use JSON logging, or on the built-in HTTP attributes.
  • Grepping a streamed tail for history. Without --lines, --since or --until, the CLI only streams new lines. Add a window to fetch the past.
  • Blaming one bad replica on the code. If the service runs several replicas, switch the metrics view from Sum to Replica, and filter logs with replica:<replica_id>. One replica pinned at full CPU while the others idle points at that instance.

Having Polylane search Railway errors for you

Polylane connects to Railway, so it sees when a service starts throwing errors and works out why from the logs and recent changes. It detects production issues and fixes them by opening pull requests, and the fix arrives ready for your team’s normal review.

Running on Railway? See how Polylane monitors Railway in production.

Common questions.

How do I filter Railway logs to show only errors?

Type `@level:error` in the Log Explorer or the deployment log panel. From the CLI, run `railway logs --lines 100 --filter "@level:error"`. This works best with single-line JSON logs that include a level field, because plain stdout lines are stored as info.

How do I find 500 errors in Railway HTTP logs?

Use `@httpStatus:500..599` for server errors or `@httpStatus:>=400` for all 4xx and 5xx responses. From the terminal, run `railway logs --http --status 500..599`. Add `@path:/your/route` to narrow it to one endpoint.

Why does every log line show as an error in Railway?

Railway converts anything written to stderr to level error. If your logger writes ordinary output to stderr, every line is marked as an error. Send normal output to stdout, or emit JSON with an explicit level field.

How far back can I search Railway logs?

It depends on your plan: 3 days on Free, 7 days on Trial and Hobby, 30 days on Pro, and up to 90 days on Enterprise. Upgrading restores logs that had aged out. For longer retention, forward stdout with Vector or Fluent Bit, or send logs with OpenTelemetry.

How do I search logs across all services in a Railway environment?

Click Observability in the top navigation to open the Log Explorer, which searches environment logs from every service together. Use `@service:<service_id>` to include a service and `-@service:<service_id>` to exclude one, such as your Postgres service.

Why are some of my Railway logs missing?

Railway drops lines beyond 500 per second per replica and prints a rate limit warning with the number of messages dropped. Reduce verbosity, log minified JSON and sample high-frequency events. Also check that the logs are still within your plan's retention period.

How do I see the logs around an error line?

Right-click the log line in the Railway dashboard and choose View in Context. It shows the lines around it, which usually include the stack trace and the request that triggered the error.

Can I get Railway logs as JSON for scripting?

Yes. Add `--json` to `railway logs`, for example `railway logs --http --status 500..599 --since 1h --json`. The output is newline-delimited JSON with camelCase fields such as `path`, `httpStatus` and `requestId`, which you can pipe into jq.

Sources (6)

  1. Logs | Railway Docs
  2. railway logs | Railway CLI Docs
  3. Debug a Production Incident with Logs, Metrics, and Traces | Railway Guides
  4. Logs, Metrics, and Traces: What Does Each Signal Tell You? | Railway Blog
  5. Polylane documentation
  6. Polylane full content

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

More in this series

Platform playbooks

  1. 1 How to Monitor a Django App on Render
  2. 2 How to Monitor a FastAPI App on Railway: Logs, Traces and Alerts
  3. 3 How to Monitor a Supabase App in Production
  4. 4 How to Debug Cloudflare Workers Errors: Logs, Traces and Error Codes
  5. 5 How to debug Vercel function timeouts
  6. 6 Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes
  7. 7 Cloudflare Workers error 1101: causes and how to fix it
  8. 8 Cloudflare Hyperdrive connection errors: causes and fixes
  9. 9 How to Monitor a Convex App in Production
  10. 10 How to monitor Cloudflare Durable Objects in production
  11. 11 How to roll back a Cloudflare Workers deployment
  12. 12 How to trace requests across Cloudflare Workers and service bindings
  13. 13 How to Monitor Vercel Cron Jobs: Heartbeats, Logs and Alerts
  14. 14 How to reduce Vercel Function cold starts
  15. 15 How to Monitor a Next.js App on Render
  16. 16 How to debug Modal function timeouts
  17. 17 Modal container out of memory: find the cause and fix it
  18. 18 Fix Railway “Healthcheck failed!” on deploy
  19. 19 How to Search Railway Logs for Errors: Filters, CLI and HTTP Logs

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free