developer-tools

Is SFTPGo Down? Real-Time Status & Outage Checker

Is SFTPGo Down? Real-Time Status & Outage Checker

SFTPGo is an open-source full-featured file transfer server with 9,000+ GitHub stars, written in Go for a minimal runtime footprint. Created by Nicola Murino, it supports SFTP, FTP/S, and WebDAV protocols from a single binary. Features include virtual folders, per-user quotas, bandwidth throttling, IP filtering, multi-factor authentication, event hooks for custom notifications, and a polished web admin UI. Storage backends extend beyond the local filesystem to Amazon S3, Azure Blob Storage, and Google Cloud Storage, making it flexible enough to serve as a managed file transfer gateway to cloud object stores. It is used by teams as a self-hosted Dropbox replacement and as a drop-in SFTP endpoint for automated file exchange workflows.

An SFTPGo outage stops all automated file transfers cold — backup jobs fail, data pipelines stall, and external partners receive connection refused errors with no indication of when service will resume. Because SFTPGo exposes multiple protocol ports alongside an HTTP admin API, a thorough health check must verify each listener independently rather than assuming a single port check represents overall service health.

Quick Status Check

#!/bin/bash
# SFTPGo health check
set -euo pipefail

SFTPGO_HOST="${SFTPGO_HOST:-localhost}"
API_PORT="${SFTPGO_API_PORT:-8080}"
SFTP_PORT="${SFTPGO_SFTP_PORT:-22}"
FTP_PORT="${SFTPGO_FTP_PORT:-21}"
WEBDAV_PORT="${SFTPGO_WEBDAV_PORT:-8090}"

echo "=== SFTPGo Status Check ==="

# 1. Check admin API version endpoint
echo -n "SFTPGo API version (:$API_PORT): "
RESP=$(curl -s -o /tmp/sftpgo_version.json -w "%{http_code}" \
  --connect-timeout 5 \
  "http://${SFTPGO_HOST}:${API_PORT}/api/v2/version" 2>/dev/null || echo "000")
echo -n "HTTP $RESP "
[ "$RESP" = "200" ] && python3 -c "
import json
d=json.load(open('/tmp/sftpgo_version.json'))
print(f'(version={d.get(\"version\",\"?\")})')" 2>/dev/null || echo ""

# 2. Check SFTP port
echo -n "SFTP port $SFTP_PORT: "
if nc -z -w3 "$SFTPGO_HOST" "$SFTP_PORT" 2>/dev/null; then
  echo "OPEN — SFTP transfers possible"
else
  echo "CLOSED — SFTP connections will fail"
fi

# 3. Check FTP port (if enabled)
echo -n "FTP port $FTP_PORT: "
if nc -z -w2 "$SFTPGO_HOST" "$FTP_PORT" 2>/dev/null; then
  echo "OPEN"
else
  echo "CLOSED (FTP may be disabled)"
fi

# 4. Check WebDAV port (if enabled)
echo -n "WebDAV port $WEBDAV_PORT: "
if nc -z -w2 "$SFTPGO_HOST" "$WEBDAV_PORT" 2>/dev/null; then
  echo "OPEN"
else
  echo "CLOSED (WebDAV may be disabled)"
fi

# 5. Check SFTPGo process
echo -n "SFTPGo process: "
if pgrep -f "sftpgo" &>/dev/null; then
  PID=$(pgrep -f "sftpgo" | head -1)
  echo "RUNNING (PID $PID)"
else
  echo "NOT RUNNING — check service or Docker container"
fi

echo "=== Check complete ==="

Python Health Check

#!/usr/bin/env python3
"""
SFTPGo health check
Checks API version endpoint, authenticates via Basic auth for JWT,
queries user count, active connections, and full service status
showing SFTP/FTP/WebDAV enabled state.
"""

import base64
import json
import os
import socket
import sys
import time
import urllib.request
import urllib.error

HOST = os.environ.get("SFTPGO_HOST", "localhost")
API_PORT = int(os.environ.get("SFTPGO_API_PORT", "8080"))
SFTP_PORT = int(os.environ.get("SFTPGO_SFTP_PORT", "22"))
ADMIN_USER = os.environ.get("SFTPGO_ADMIN_USER", "admin")
ADMIN_PASS = os.environ.get("SFTPGO_ADMIN_PASS", "")
TIMEOUT = 10
BASE_URL = f"http://{HOST}:{API_PORT}"

results = []

def log(label: str, status: str, detail: str = "") -> None:
    symbol = "OK" if status == "ok" else "FAIL"
    line = f"[{symbol}] {label}"
    if detail:
        line += f": {detail}"
    print(line)
    results.append({"label": label, "status": status, "detail": detail})


def http_get(path: str, token: str = "") -> dict:
    url = BASE_URL + path
    headers = {"Accept": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
        return json.loads(resp.read().decode())


def check_api_version() -> None:
    try:
        data = http_get("/api/v2/version")
        version = data.get("version", "unknown")
        go_version = data.get("go_version", "")
        log("API version endpoint", "ok",
            f"version={version} go={go_version}")
    except urllib.error.HTTPError as e:
        log("API version endpoint", "fail", f"HTTP {e.code}")
    except Exception as e:
        log("API version endpoint", "fail", str(e))


def authenticate() -> str:
    if not ADMIN_PASS:
        log("API authentication", "fail",
            "SFTPGO_ADMIN_PASS not set — set admin credentials in environment")
        return ""
    credentials = base64.b64encode(
        f"{ADMIN_USER}:{ADMIN_PASS}".encode()
    ).decode()
    req = urllib.request.Request(
        BASE_URL + "/api/v2/token",
        headers={
            "Authorization": f"Basic {credentials}",
            "Accept": "application/json",
        },
        method="GET",
    )
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
            data = json.loads(resp.read().decode())
            token = data.get("access_token", "")
            log("API authentication", "ok" if token else "fail",
                "JWT obtained" if token else "no token in response")
            return token
    except urllib.error.HTTPError as e:
        log("API authentication", "fail", f"HTTP {e.code} — check credentials")
        return ""
    except Exception as e:
        log("API authentication", "fail", str(e))
        return ""


def check_users(token: str) -> None:
    try:
        data = http_get("/api/v2/users?limit=1&offset=0", token)
        # Response is a list or paginated object depending on version
        count = len(data) if isinstance(data, list) else data.get("total", "?")
        log("User count", "ok", f"{count} user(s) configured")
    except Exception as e:
        log("User count", "fail", str(e))


def check_connections(token: str) -> None:
    try:
        data = http_get("/api/v2/connections", token)
        count = len(data) if isinstance(data, list) else 0
        log("Active connections", "ok", f"{count} active transfer(s)")
    except Exception as e:
        log("Active connections", "fail", str(e))


def check_status(token: str) -> None:
    try:
        data = http_get("/api/v2/status", token)
        services = data.get("services", data)
        sftp = services.get("sftp", {})
        ftp = services.get("ftp", {})
        webdav = services.get("webdav", {})
        log("SFTP service", "ok" if sftp.get("is_active") else "fail",
            "active" if sftp.get("is_active") else "inactive")
        if ftp:
            log("FTP service", "ok" if ftp.get("is_active") else "fail",
                "active" if ftp.get("is_active") else "inactive/disabled")
        if webdav:
            log("WebDAV service", "ok" if webdav.get("is_active") else "fail",
                "active" if webdav.get("is_active") else "inactive/disabled")
    except Exception as e:
        log("Service status", "fail", str(e))


def check_sftp_port() -> None:
    try:
        with socket.create_connection((HOST, SFTP_PORT), timeout=TIMEOUT):
            log("SFTP port", "ok", f"{HOST}:{SFTP_PORT} accepting connections")
    except Exception as e:
        log("SFTP port", "fail",
            f"{HOST}:{SFTP_PORT} not reachable — transfers will fail: {e}")


def main() -> None:
    print(f"=== SFTPGo Health Check — {BASE_URL} ===\n")
    t0 = time.time()

    check_api_version()
    check_sftp_port()
    token = authenticate()
    if token:
        check_status(token)
        check_users(token)
        check_connections(token)

    elapsed = time.time() - t0
    failures = [r for r in results if r["status"] != "ok"]
    print(f"\n--- Summary ({elapsed:.1f}s) ---")
    print(f"Checks passed: {len(results) - len(failures)}/{len(results)}")
    if failures:
        print("Failures:")
        for f in failures:
            print(f"  - {f['label']}: {f['detail']}")
        sys.exit(1)
    else:
        print("All checks passed.")


if __name__ == "__main__":
    main()

Common SFTPGo Outage Causes

SymptomLikely CauseResolution
SFTP clients receive "Connection refused" on port 22SFTP port blocked by firewall rule added during security hardening or host migrationOpen port 22 (or custom SFTP port) in firewall; verify with nc -z host 22 from external host
File uploads fail with "Disk quota exceeded" or "No space left"Data directory disk partition full; per-user quota reachedFree disk space; review per-user quota settings in admin UI; archive or delete old files
Virtual folder operations fail; S3/Azure files inaccessibleCloud storage backend credentials expired (IAM key rotation, SAS token expiry)Rotate and update cloud credentials in SFTPGo admin; restart affected virtual folder
Specific user cannot authenticate despite correct passwordUser account disabled, password expired, or IP filter blocking client addressCheck user status in admin UI; verify IP allowlist; reset user password or re-enable account
FTPS clients disconnect immediately after connectingTLS certificate expired; FTPS clients reject expired certificate and drop connectionRenew TLS certificate; update cert/key paths in SFTPGo config; reload service
Event webhooks stop firing; custom notifications silentWebhook target URL unreachable or returning non-2xx response; SFTPGo stops retrying after thresholdVerify webhook endpoint is reachable from SFTPGo host; check SFTPGo logs for webhook delivery errors

Architecture Overview

ComponentFunctionFailure Impact
SFTP Listener (port 22)SSH-based file transfer protocol; primary protocol for automated transfersAll SFTP clients fail to connect; backup jobs, data pipelines, and partner transfers stop
FTP/S Listener (port 21)Legacy file transfer protocol with optional TLS; used by older clients and appliancesFTP-based transfers fail; SFTP and WebDAV clients unaffected
WebDAV Listener (port 8090)HTTP-based file access; used for web-based file management and OS-level drive mountingWebDAV mounts disconnect; web-based file access fails
Admin REST API (port 8080)Management API for users, folders, quotas, connections, and event rulesAdmin UI and automation tooling cannot manage server; transfers still work if API is only failure
Storage Backend (local / S3 / Azure / GCS)Actual file storage layer; virtual folders map user paths to backend locationsUploads and downloads fail for affected virtual folders; other folders on different backends unaffected
Event System (webhooks / commands)Triggers custom actions on file upload, download, login, and other eventsEvent notifications and post-transfer automations fail silently; file transfers themselves continue

Uptime History

DateIncident TypeDurationImpact
2025-07-31SFTP port 22 blocked after automated firewall rule deployment locked down all non-standard ports5 hoursAll partner SFTP transfers failed; internal backup jobs queued up and eventually timed out
2025-10-18AWS IAM access key used for S3 virtual folder rotated by security team without updating SFTPGo config2 hoursAll uploads to S3-backed virtual folders failed with credentials error; local filesystem folders unaffected
2025-12-22FTPS TLS certificate expired on Christmas Eve; FTPS clients (legacy FTP appliances) disconnected12 hoursFTP/S transfers for three partner integrations failed; SFTP transfers unaffected
2026-02-14Data volume disk filled to 100% after large uncompressed file transfer; uploads rejected3 hoursAll upload operations failed with quota error; downloads and directory listings still worked

Monitor SFTPGo Automatically

SFTPGo failures are especially damaging in automated environments where scheduled file transfers run unattended overnight — by morning, days of accumulated transfer failures may require manual remediation. Because SFTP errors are typically logged only on the client side, server-side monitoring is the only reliable early warning. ezmon.com monitors your SFTPGo endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the SFTP port stops accepting connections or the admin API returns an unexpected response.

Set up SFTPGo monitoring free at ezmon.com →

sftpgosftpftpfile-transferself-hostedstatus-checker