How Do I Set a Timeout on a Job?

Set timeout-minutes on the job and a tighter one on any step that can hang, so a stuck GitHub Actions job fails in minutes instead of billing for hours.

Last verified:

Set timeout-minutes on the job as a backstop, then set a tighter timeout-minutes on any step that can block: a service wait, a network fetch, an integration suite. With neither key present GitHub applies a default of 360 minutes, so one hung step holds a runner for six hours and bills every minute of it.

Answer

The key lives at two levels of workflow YAML, and GitHub documents each one separately. At the job level, jobs.<job_id>.timeout-minutes is the maximum number of minutes to let a job run before GitHub automatically cancels it, with a default of 360. At the step level, jobs.<job_id>.steps[*].timeout-minutes is the maximum number of minutes to run the step before the process is killed, with a documented maximum of 360 on both GitHub-hosted and self-hosted runners. Both keys take positive integers (checked on 2026-08-13).

This workflow uses both. The job value covers checkout, install, and anything else that stalls; the two step values sit on the parts that talk to a service.

name: integration
on:
  pull_request:

jobs:
  integration:
    runs-on: warp-ubuntu-latest-x64-8x
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - name: Wait for the service to accept connections
        timeout-minutes: 4
        run: ./scripts/wait-for-service.sh
      - name: Integration suite
        timeout-minutes: 12
        run: npm run test:integration
      - name: Collect service logs
        if: always()
        run: docker compose logs > service.log
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: service-logs
          path: service.log

Checkout and install take about 3 minutes here. A service that never becomes ready kills the wait step at minute 7 of the job rather than at minute 360. The two if: always() steps still run while the job is failing, so the logs that explain the hang land as an artifact instead of dying with the machine.

Runner labels are the warp- tags from the cloud runners documentation; the timeout keys behave identically on GitHub-hosted labels, since both keys are GitHub Actions features rather than runner features.

Detail

What the default costs on each runner size

A job with no timeout-minutes anywhere runs to the 360 minute default. Runner minutes bill on execution time, so the machine cost of one hung job is the rate multiplied by the minutes it was allowed to sit there. The per minute rates below come from the pricing page and the cloud runners documentation, checked on 2026-08-13.

Runner labelShapeRateNo timeout (360 min)Job timeout-minutes: 25Step timeout, stops at 7 min
warp-ubuntu-latest-x64-4x4 vCPU, 16 GB$0.008/min$2.88$0.20$0.056
warp-ubuntu-latest-x64-8x8 vCPU, 32 GB$0.016/min$5.76$0.40$0.112
warp-ubuntu-latest-arm64-8x8 vCPU, 32 GB$0.012/min$4.32$0.30$0.084
warp-windows-latest-x64-8x8 vCPU, 32 GB$0.032/min$11.52$0.80$0.224
warp-macos-15-arm64-6x6 vCPU, 22 GB$0.08/min$28.80$2.00$0.56

The macOS row is the one that changes behavior on most teams. A single hung iOS job at the default bills $28.80, which is more than the 360 minute default costs on all four of the other labels combined.

Now scale it. Assume 12 jobs a month hang on the integration step, on warp-ubuntu-latest-x64-8x:

  • At the 360 minute default: 12 x 360 = 4,320 minutes, or $69.12 a month for zero passing builds.
  • With the job backstop at 25 minutes: 12 x 25 = 300 minutes, or $4.80.
  • With the step timeout at 4 minutes on the wait: 12 x 7 = 84 minutes, or $1.34.

The step timeout removes $67.78 a month against the default on that one job, and it removes 4,236 minutes of a developer waiting on a check that was never going to turn green.

The same 4,320 minutes bill $95.04 on GitHub's 8-core Linux larger runner. warp-ubuntu-latest-x64-8x (8 vCPU, 32 GB) costs $0.016 per minute against $0.022 per minute for the 8-core Linux larger runner (8 vCPU, 32 GB): 27 percent lower list price (GitHub Actions minute multipliers, GitHub list price checked on 2026-08-13). The timeout is still the larger lever, since it changes the minute count rather than the rate.

Choosing the two values

Set the step value from the observed p95 duration of that step plus headroom, and set the job value above the sum of the steps plus a few minutes for provisioning and checkout. A value chosen from the average kills healthy slow runs, and a retried run costs more machine time than the timeout saved.

Three rules cover the rest:

  1. Every job needs its own key. There is no workflow level timeout, so a workflow with six jobs needs six values.
  2. Matrix legs inherit the job value independently. One leg hitting the limit is cancelled on its own while the siblings continue.
  3. A value above the platform ceiling has no effect past that ceiling. GitHub's limits reference puts job execution at 6 hours on GitHub-hosted runners and 5 days on self-hosted runners, so timeout-minutes: 600 on a GitHub-hosted runner still ends at six hours.

What a timeout does not fix

A timeout ends work. It says nothing about why the work hung, and it does nothing for a job that was slow rather than stuck. If jobs are queued instead of running, the clock has not started and no timeout applies; the common issues documentation covers runner group access, bot permissions, and workflow restrictions, which are the usual reasons a job sits unclaimed.

For work that legitimately runs for hours, a tight timeout converts a long job into a failed job. Split it instead, following long running jobs on GitHub Actions. For the ordinary case of jobs that are simply slower than they need to be, start with the guide to speeding up GitHub Actions, which covers caching, runner sizing, and parallelism. The definitions and the full ceiling table sit in the job timeout glossary entry.

What is the default timeout for a GitHub Actions job?

360 minutes. GitHub's workflow syntax reference documents 360 as the default for jobs.<job_id>.timeout-minutes, so a job with no timeout-minutes key holds its machine for six hours before the platform cancels it. On warp-ubuntu-latest-x64-8x at $0.016 per minute that single hung job bills $5.76.

Can I set one timeout for a whole workflow?

No. GitHub exposes timeout-minutes at the job level and the step level only, so a workflow with six jobs needs the key on all six. Matrix legs each inherit the job value independently, which means one hung leg is cancelled at the limit while its siblings keep running. The job timeout glossary entry lists the platform ceilings that apply around both keys.

Does a job that hits its timeout still get billed?

Yes. Runner minutes bill on execution time, so a job cancelled at minute 25 bills 25 minutes and a job cancelled at the 360 minute default bills 360. The timeout is the lever because it decides how many of those minutes exist. The same mechanic applies to every red run, as covered in whether a failed GitHub Actions job is billed.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.