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
| Symptom | Likely Cause | Resolution |
|---|---|---|
| SFTP clients receive "Connection refused" on port 22 | SFTP port blocked by firewall rule added during security hardening or host migration | Open 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 reached | Free disk space; review per-user quota settings in admin UI; archive or delete old files |
| Virtual folder operations fail; S3/Azure files inaccessible | Cloud 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 password | User account disabled, password expired, or IP filter blocking client address | Check user status in admin UI; verify IP allowlist; reset user password or re-enable account |
| FTPS clients disconnect immediately after connecting | TLS certificate expired; FTPS clients reject expired certificate and drop connection | Renew TLS certificate; update cert/key paths in SFTPGo config; reload service |
| Event webhooks stop firing; custom notifications silent | Webhook target URL unreachable or returning non-2xx response; SFTPGo stops retrying after threshold | Verify webhook endpoint is reachable from SFTPGo host; check SFTPGo logs for webhook delivery errors |
Architecture Overview
| Component | Function | Failure Impact |
|---|---|---|
| SFTP Listener (port 22) | SSH-based file transfer protocol; primary protocol for automated transfers | All 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 appliances | FTP-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 mounting | WebDAV mounts disconnect; web-based file access fails |
| Admin REST API (port 8080) | Management API for users, folders, quotas, connections, and event rules | Admin 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 locations | Uploads 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 events | Event notifications and post-transfer automations fail silently; file transfers themselves continue |
Uptime History
| Date | Incident Type | Duration | Impact |
|---|---|---|---|
| 2025-07-31 | SFTP port 22 blocked after automated firewall rule deployment locked down all non-standard ports | 5 hours | All partner SFTP transfers failed; internal backup jobs queued up and eventually timed out |
| 2025-10-18 | AWS IAM access key used for S3 virtual folder rotated by security team without updating SFTPGo config | 2 hours | All uploads to S3-backed virtual folders failed with credentials error; local filesystem folders unaffected |
| 2025-12-22 | FTPS TLS certificate expired on Christmas Eve; FTPS clients (legacy FTP appliances) disconnected | 12 hours | FTP/S transfers for three partner integrations failed; SFTP transfers unaffected |
| 2026-02-14 | Data volume disk filled to 100% after large uncompressed file transfer; uploads rejected | 3 hours | All 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.