Setup
Traefik
If you’re new to Traefik and you haven’t read my Traefik 2.5 quick-start guide, please do so before proceeding.
We’ll create an SFTP entrypoint for port 2222/TCP
in the Static Configuration, and define the rest of SFTPGo’s entry in the Dynamic Configuration.
Static Configuration
Adding the entrypoint in, the relevant section would appear in traefik.toml
as:
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web.http]
[entryPoints.web.http.redirections]
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
permanent = true
[entryPoints.websecure]
address = ":443"
[entryPoints.sftp]
address = ":2222/tcp"
Dynamic Configuration
SFTPGo provides multiple services that span across several ports:
- SFTP
- HTTP
- Telemetry
- WebDAV
We’re going to glue all of this together in Traefik, with two entrypoints:
- HTTPS: port
443/TCP
- SFTP: port
2222/TCP
Traefik will route directly to SFTP via TCP with the Proxy Protocol. Prioritized rulesets with path definitions will filter all other requests that Traefik will then forward to the appropriate service. Attributes for the HTTPS requests to keep in mind:
- Web interface for administrators and clients.
/web/admin
— Restricted to bastion hosts (i.e. VPN servers)./web/client
— Accessible to the public.
- API for scripts to programatically administer.
/api
— Restricted to bastion hosts and/or jump hosts (i.e. VPN servers and secure machines).
- Assets — Multimedia resources (e.g. images, JavaScript, and CSS).
/static
— Accessible to the public.
- Index
/
— By default, SFTPGo will redirect these requests to the web interface for clients, but I’ll demonstrate how to redirect these requests to the web interface for administrators.
- Telemetry — Restricted to jump hosts (i.e. secured servers for administrative duties, such as processing raw data with Grafana).
/healthz
— Health checks./metrics
— Prometheus metrics./debug/pprof
— Profiling with pprof.
- WebDAV — Lower priority pattern matching and if none of the other rules match. An example is if Traefik answers for
example.com
and you were using WebDAV to downloadprofile.png
. The requst with the GET method would be forhttps://example.com/profile.png
and would show up to Traefik as/profile.png
. Since SFTPGo has such clearly defined URLs that we’ve defined in Traefik and have prioritized above this one, we can assume that anything else that doesn’t match rules for known paths should be a direct query from the WebDAV service.
Now we only need a single HTTPS entrypoint instead of three. Thus, the contents of /srv/traefik/traefik.d/sftpgo.toml
are:
# Web Admin — Privileged user
[http.routers.Router-SFTPGo-Web-Admin]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/web/admin`)"
priority = 100
service = "SFTPGo-Web"
tls = true
middlewares = [
"bastion-hosts",
]
# API — Privileged user
[http.routers.Router-SFTPGo-API]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/api`)"
priority = 100
service = "SFTPGo-Web"
tls = true
middlewares = [
"bastion-hosts",
]
# Assets
[http.routers.Router-SFTPGo-Assets]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/static`)"
priority = 100
service = "SFTPGo-Web"
tls = true
# Web Client
[http.routers.Router-SFTPGo-Web-Client]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/web/client`)"
priority = 100
service = "SFTPGo-Web"
tls = true
# Index
[http.routers.Router-SFTPGo-Index]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && Method(`GET`) && Path(`/`)"
priority = 100
service = "SFTPGo-Web"
tls = true
middlewares = [
"SFTPGo-Redirect-Index",
]
# Telemetry : Health checks — https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md#telemetry-server
[http.routers.Router-SFTPGo-Telemetry-Health]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/healthz`)"
priority = 100
service = "SFTPGo-Telemetry"
tls = true
middlewares = [
"bastion-hosts",
]
# Telemetry : Prometheus metrics — https://github.com/drakkan/sftpgo/blob/main/docs/metrics.md
[http.routers.Router-SFTPGo-Telemetry-Metrics]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/metrics`)"
priority = 100
service = "SFTPGo-Telemetry"
tls = true
middlewares = [
"bastion-hosts",
]
# Telemetry : Profiling — https://github.com/drakkan/sftpgo/blob/main/docs/profiling.md
[http.routers.Router-SFTPGo-Telemetry-Profiling]
entrypoints = ["websecure"]
rule = "Host(`example.com`) && PathPrefix(`/debug/pprof`)"
priority = 100
service = "SFTPGo-Telemetry"
tls = true
middlewares = [
"bastion-hosts",
]
# WebDAV
[http.routers.Router-SFTPGo-WebDAV]
entrypoints = ["websecure"]
rule = "Host(`example.com`)"
priority = 90
service = "SFTPGo-WebDAV"
tls = true
# SFTP
[tcp.routers.SFTP]
entryPoints = ["sftp"]
rule = "HostSNI(`*`)" # Catch every request (only available rule for non-tls routers.)
service = "SFTPGo-SFTP"
# Middlewares
[http.middlewares]
# Index page
[http.middlewares.SFTPGo-Redirect-Index.redirectRegex]
regex = ".*"
replacement = "/web/admin"
# Services
# TCP
[tcp.services]
[tcp.services.SFTPGo-SFTP.loadBalancer]
# Enable Proxy Protocol
[tcp.services.SFTPGo-SFTP.loadBalancer.proxyProtocol]
version = 2
# TCP: SFTP
[[tcp.services.SFTPGo-SFTP.loadBalancer.servers]]
address = "sftpgo:1111"
# HTTP
[http.services]
# HTTP: Web
[http.services.SFTPGo-Web.loadBalancer]
[[http.services.SFTPGo-Web.loadBalancer.servers]]
url = "http://sftpgo:2222"
# HTTP: Telemetry
[http.services.SFTPGo-Telemetry.loadBalancer]
[[http.services.SFTPGo-Telemetry.loadBalancer.servers]]
url = "http://sftpgo:3333"
# HTTP: WebDAV
[http.services.SFTPGo-WebDAV.loadBalancer]
[[http.services.SFTPGo-WebDAV.loadBalancer.servers]]
url = "http://sftpgo:4444"
Docker Compose
I prefer to configure SFTPGo almost entirely with its environment variables, and have based several of the values below from the default sftpgo.json file found in the repository. In my prior Traefik article, I covered how I go about using Link-local address segments to prevent my containers from conflicting with other networks (and the host itself), and I am doing the same here.
Thus, the contents of /etc/docker/compose/sftpgo/docker-compose.yaml
are:
---
version: "3.0"
networks:
default:
driver: bridge
ipam:
config:
- subnet: 169.254.0.0/29
services:
#-------------------------------------------------------------------------------
traefik:
image: traefik:v2.6
container_name: traefik
depends_on:
- "sftpgo"
# Public-facing ports
ports:
- "80:80/tcp" # HTTP
- "443:443/tcp" # HTTPS
- "2222:2222/tcp" # SFTP
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Listen to the Docker events
- /srv/traefik/traefik.toml:/traefik.toml:ro # Static config.
- /srv/traefik/traefik.d:/etc/traefik:ro # Dynamic config.
- /srv/traefik/logs:/logs # Diagnosis / Traffic
- /srv/traefik/users:/users:ro # Basic auth. users
- /srv/traefik/plugins:/plugins-local:ro # Local plugins without Traefik Pilot
- /srv/letsencrypt:/letsencrypt:ro # Offsite certs.
- /etc/ssl/certs/:/cacerts:ro # Certificate Authorities
restart: "always"
#-------------------------------------------------------------------------------
sftpgo:
image: drakkan/sftpgo:v2-alpine
container_name: sftpgo
# Peer-facing ports
expose:
- "1111/tcp"
- "2222/tcp"
- "3333/tcp"
- "4444/tcp"
environment:
# Proxy Protocol
- "SFTPGO_COMMON__PROXY_PROTOCOL=2"
- "SFTPGO_COMMON__PROXY_ALLOWED=169.254.0.0/29"
# SFTP
- "SFTPGO_SFTPD__BINDINGS__0__PORT=1111"
# HTTP
- "SFTPGO_HTTPD__BINDINGS__0__PORT=2222"
- "SFTPGO_HTTPD__BINDINGS__0__PROXY_ALLOWED=169.254.0.0/29"
# Telemetry
- "SFTPGO_TELEMETRY__BIND_PORT=3333"
- "SFTPGO_TELEMETRY__BIND_ADDRESS=0.0.0.0"
# Uncomment if you want to profile SFTPGo, see https://github.com/drakkan/sftpgo/blob/main/docs/profiling.md
# - "SFTPGO_TELEMETRY__ENABLE_PROFILER=true"
# WebDAV
- "SFTPGO_WEBDAVD__BINDINGS__0__PORT=4444"
- "SFTPGO_WEBDAVD__BINDINGS__0__PROXY_ALLOWED=169.254.0.0/29"
# Defender (similar concept as Fail2ban) — see https://github.com/drakkan/sftpgo/blob/main/docs/defender.md
- "SFTPGO_COMMON__DEFENDER__ENABLED=true"
- "SFTPGO_COMMON__DEFENDER__BAN_TIME=30"
- "SFTPGO_COMMON__DEFENDER__BAN_TIME_INCREMENT=50"
- "SFTPGO_COMMON__DEFENDER__THRESHOLD=15"
- "SFTPGO_COMMON__DEFENDER__OBSERVATION_TIME=30"
volumes:
- /srv/sftpgo/home:/srv/sftpgo/data
- /srv/sftpgo/backups:/srv/sftpgo/backups
- /srv/sftpgo/config:/var/lib/sftpgo
restart: unless-stopped
Table of Contents