Introduction

Self-hosted GitHub Actions runners execute workflows on your own infrastructure, providing custom environments, specialized hardware access, and control over the execution environment. Unlike GitHub-hosted runners that are automatically maintained, self-hosted runners require ongoing operational management to remain healthy and connected.

A runner goes offline when it loses its persistent connection to GitHub's runner service, when the runner process crashes, or when the host machine becomes unavailable. The runner application maintains a long-lived WebSocket connection to GitHub, polling for job assignments. Any disruption to this connection—network issues, process crashes, resource exhaustion—causes the runner to appear offline in GitHub's interface.

Understanding the runner lifecycle, connection management, and failure modes is essential for maintaining reliable self-hosted runner infrastructure. Runners that go offline unexpectedly cause queued jobs to wait indefinitely, blocking CI/CD pipelines.

Symptoms

When self-hosted GitHub Actions runners go offline, you will observe these symptoms:

  • Runner shows as "Offline" in GitHub Actions settings
  • Last seen timestamp is old (hours or days)
  • Queued jobs pile up waiting for the runner
  • Workflow runs fail with "no runner available"
  • Runner host is running but runner service is stopped
  • Recent infrastructure changes preceded the outage

GitHub UI showing offline runner:

bash
Runners
- my-runner-01
  Status: Offline
  Labels: self-hosted, linux, x64
  Last seen: 2 hours ago

Queued workflow waiting for offline runner:

bash
Job status: Queued
Message: Waiting for a runner to pick up this job
Runner requirements: self-hosted, linux, x64

Runner logs showing connection failure:

bash
[2026-01-15 10:30:45] Runner listener has stopped
[2026-01-15 10:30:46] Error: Connection to GitHub service lost
[2026-01-15 10:30:47] Attempting to reconnect...
[2026-01-15 10:30:52] Reconnect failed: Network unreachable

Common Causes

Several factors cause self-hosted runner offline issues:

  1. 1.Runner process stopped or crashed: The runner service (actions.runner.*) stopped due to manual intervention, crash, or system shutdown without proper service management.
  2. 2.Network connectivity lost: The runner cannot reach GitHub's runner service due to network issues, firewall changes, or DNS resolution failures.
  3. 3.Host machine shutdown or reboot: The VM or machine hosting the runner was stopped, rebooted, or experienced hardware failure without the runner service starting automatically.
  4. 4.Resource exhaustion: Disk full, out of memory, or CPU starvation caused the runner process to crash or become unresponsive.
  5. 5.Runner registration token expired: During initial setup, if registration failed, the runner may appear in GitHub but cannot connect.
  6. 6.Proxy or firewall blocking outbound connections: Corporate firewalls block the runner's outbound HTTPS connections to GitHub.
  7. 7.Runner version incompatible: Outdated runner version may fail to connect after GitHub updates the runner service API.
  8. 8.TLS/SSL certificate issues: System certificate store issues prevent the runner from validating GitHub's TLS certificates.

Step-by-Step Fix

Follow these steps to diagnose and resolve self-hosted runner offline issues:

Step 1: Check runner status in GitHub

Verify the runner's online state:

```bash # Check runner status via GitHub CLI gh api repos/:owner/:repo/actions/runners --jq '.runners[] | {name, status, last_seen: .updated_at}'

# Organization-level runners gh api orgs/:org/actions/runners --jq '.runners[] | {name, status}'

# For offline runner: { "name": "my-runner-01", "status": "offline", "last_seen": "2026-01-15T08:30:00Z" } ```

Step 2: Check runner service on host

Verify the runner process is running:

```bash # Check systemd service status sudo systemctl status actions.runner.*

# Expected output for healthy runner: ● actions.runner.owner-repo.service - GitHub Actions Runner (owner-repo) Loaded: loaded (/etc/systemd/system/actions.runner.owner-repo.service; enabled) Active: active (running) since Mon 2026-01-15 10:00:00 UTC

# Check if runner process exists ps aux | grep Runner.Listener

# For manually started runners pgrep -f "run.sh" ```

Step 3: Start or restart the runner service

If the service is stopped:

```bash # Start the runner service sudo systemctl start actions.runner.owner-repo.runner-name

# Restart if already running but having issues sudo systemctl restart actions.runner.*

# Ensure service starts on boot sudo systemctl enable actions.runner.*

# If running manually, start the runner cd /home/runner/actions-runner ./run.sh & # Not recommended for production ```

Step 4: Check runner diagnostic logs

Examine runner logs for connection issues:

```bash # View runner diagnostic logs ls -la /home/runner/actions-runner/_diag/

# Check latest log file tail -100 /home/runner/actions-runner/_diag/Runner_$(date +%Y%m%d).log

# Look for specific errors grep -r "error|fail|disconnect" /home/runner/actions-runner/_diag/ | tail -50

# Common error patterns: # - "Cannot connect to GitHub runner service" # - "WebSocket connection closed" # - "Error refreshing token" # - "Network is unreachable" ```

Step 5: Test network connectivity

Verify the runner can reach GitHub:

```bash # Test HTTPS connectivity to GitHub curl -v https://github.com/_runner/dispatch

# Check DNS resolution dig github.com nslookup github.com

# Test specific runner endpoints curl -v https://api.github.com/actions/runner/registration

# If behind proxy, test with proxy curl -v --proxy http://proxy.company.com:8080 https://github.com

# Check for network errors ping github.com traceroute github.com ```

Step 6: Check host resources

Verify sufficient resources:

```bash # Check disk space (runner needs space for work directories) df -h # Ensure adequate space on /home/runner and /tmp

# Check memory free -h # Look for memory exhaustion

# Check for OOM kills dmesg | grep -i "out of memory"

# Check CPU load top -bn1 | head -20

# Check for resource limits ulimit -a ```

Step 7: Check proxy configuration

If runner needs proxy to reach GitHub:

```bash # Check proxy environment variables echo $HTTP_PROXY echo $HTTPS_PROXY echo $http_proxy echo $https_proxy echo $NO_PROXY

# If proxy required, configure in runner cd /home/runner/actions-runner ./config.sh --url https://github.com/owner/repo \ --token REGISTRATION_TOKEN \ --proxyurl http://proxy.company.com:8080 \ --proxyuser username \ --proxypass password

# Restart after reconfiguration sudo systemctl restart actions.runner.* ```

Step 8: Update runner if needed

Ensure runner version is current:

```bash # Check runner version /home/runner/actions-runner/run.sh --version

# Update runner to latest cd /home/runner/actions-runner ./config.sh --url https://github.com/owner/repo --token NEW_TOKEN --replace --labels self-hosted,linux,x64 sudo ./svc.sh reinstall sudo ./svc.sh start

# Or download fresh version wget https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz ```

Step 9: Re-register runner if necessary

If runner cannot reconnect after troubleshooting:

```bash # Get new registration token gh api -X POST repos/:owner/:repo/actions/runners/registration-token --jq '.token'

# Remove existing runner from GitHub gh api -X DELETE repos/:owner/:repo/actions/runners/:runner-id

# Re-register the runner cd /home/runner/actions-runner ./config.sh --url https://github.com/owner/repo \ --token NEW_REGISTRATION_TOKEN \ --labels self-hosted,linux,x64,production \ --replace

# Install as service and start sudo ./svc.sh install sudo ./svc.sh start ```

Verification

After fixing the runner, verify it's online:

```bash # Check runner status in GitHub gh api repos/:owner/:repo/actions/runners --jq '.runners[] | select(.name=="my-runner-01") | .status' # Expected: "online"

# Trigger a test workflow gh workflow run test.yml

# Watch the workflow gh run watch

# Check runner picked up the job gh run view --json jobs --jq '.jobs[] | {name, status, runner_name: .runner_name}'

# Verify in runner logs tail -20 /home/runner/actions-runner/_diag/Runner_*.log | grep -E "Running job|completed" ```

Test runner functionality:

yaml
# Test workflow to verify runner works
name: Runner Test
on: workflow_dispatch
jobs:
  test:
    runs-on: [self-hosted, linux, x64]
    steps:
      - run: echo "Runner is working!"
      - run: hostname
      - run: uname -a

Prevention

To prevent self-hosted runner offline issues:

  1. 1.Run runner as systemd service: Ensure automatic start and restart.
bash
sudo ./svc.sh install
sudo systemctl enable actions.runner.*
  1. 1.Monitor runner health: Set up external monitoring.
bash
# Simple monitoring script
#!/bin/bash
STATUS=$(gh api repos/:owner/:repo/actions/runners --jq '.runners[] | select(.name=="my-runner-01") | .status')
if [ "$STATUS" != "online" ]; then
    echo "Runner offline, attempting restart"
    sudo systemctl restart actions.runner.*
    echo "Runner restarted" | mail -s "Runner Alert" admin@example.com
fi
  1. 1.Configure auto-restart on failure: systemd handles this automatically.
ini
# In service file (auto-generated by ./svc.sh install)
[Service]
Restart=always
RestartSec=10
  1. 1.Monitor host resources: Alert before resource exhaustion.
bash
# Add to monitoring
df -h /home/runner | grep -v "Filesystem" | awk '{if ($5+0 > 80) print "Warning: Disk usage " $5}'
free | grep Mem | awk '{if ($3/$2 > 0.9) print "Warning: Memory usage > 90%"}'
  1. 1.Document runner configuration: Keep records of setup for quick recovery.
markdown
## Runner Configuration
- Name: my-runner-01
- Location: runner-01.internal (10.0.1.50)
- Labels: self-hosted, linux, x64, production
- Service: actions.runner.owner-repo.my-runner-01
- Registration: Token stored in secrets manager
- Proxy: http://proxy.company.com:8080
  1. 1.Test connectivity after network changes: Verify runner can still reach GitHub after firewall/VPN changes.
  2. 2.Keep runner updated: Periodically update to latest runner version.
bash
# Monthly update check
latest=$(curl -s https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name')
current=$(/home/runner/actions-runner/run.sh --version 2>/dev/null || echo "unknown")
if [ "$latest" != "$current" ]; then
    echo "Update available: $latest (current: $current)"
fi

Related Articles

  • [WordPress troubleshooting: Fix IAM Access Denied 403 - Complete Tro](fix-iam-access-denied-403)
  • [GitHub Actions Artifact Expired or 403 Download Failed](github-actions-artifact-expired-403-download-failed)
  • [Fix Github Actions Artifact Upload Failed File Too Large 5gb Limit Issue in GitHub Actions](github-actions-artifact-upload-failed-file-too-large-5gb-limit)
  • [Fix Github Actions Aws S3 Deploy Credentials Expired Rotate Issue in GitHub Actions](github-actions-aws-s3-deploy-credentials-expired-rotate)
  • [Fix Github Actions Cache Evicted Manually Workflow Fails Download Issue in GitHub Actions](github-actions-cache-evicted-manually-workflow-fails-download)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "GitHub Actions Self-Hosted Runner Offline Because the Connection Was Lost", "description": "Resolve self-hosted GitHub Actions runner offline errors by checking the runner service, network connectivity, disk and memory pressure, and auto-restart behavior.", "url": "https://www.fixwikihub.com/github-actions-self-hosted-runner-offline-connection-lost", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-22T18:12:17.924Z", "dateModified": "2026-01-22T18:12:17.924Z" } </script>