A minimal, self-hosted SFTP server written in Go. It supports password and public‑key authentication backed by a SQL user store (SQLite by default, PostgreSQL optional). Each user is confined to a per‑user root directory with configurable permissions (read/list/write/delete). Logs are written to a rotating log file.
- Protocols: SSH/SFTP
- Auth methods: Password (bcrypt) and/or SSH public key
- Per‑user virtual filesystem roots with path‑traversal protection
- Permission bitmask per user: 1=Read, 2=List, 4=Write, 8=Delete
- Auto‑applies DB schema at startup if the
sftp_userstable is missing (usessqlite_ddl.sqlorpostgres_ddl.sql) - Rotating structured logs via lumberjack + zap
- Setstat support: chmod, timestamps, chown (non-Windows), and safe truncate (Size>0)
- Language: Go (Go modules)
- Go version in go.mod: 1.24.4
- Package manager: Go modules (go.mod/go.sum)
- Main entry point:
main.go - Executable artifact (example present):
v-sftp.exe - Key dependencies:
- github.com/pkg/sftp — SFTP server machinery
- golang.org/x/crypto — SSH, bcrypt
- go.uber.org/zap — logging
- gopkg.in/natefinch/lumberjack.v2 — log rotation
- github.com/joho/godotenv — environment file loading
- database drivers: modernc.org/sqlite (default), github.com/lib/pq (PostgreSQL)
- Go toolchain matching the version in go.mod (1.24.x) or newer
- OS: Windows/Linux/macOS supported by Go and the selected DB driver
- If using SQLite (default): no external DB needed
- If using PostgreSQL: reachable PostgreSQL instance and a valid DSN
The server reads configuration from a .env file at startup (required). If the .env file is missing, the process exits with an error.
- DB_TYPE: Database driver name (default:
sqlite). Supported values:sqlite,postgres. - DB_DSN: Database DSN/connection string (default:
./data/sftp.db).- SQLite example:
DB_TYPE=sqlite,DB_DSN=./data/sftp.db - PostgreSQL example:
DB_TYPE=postgres,DB_DSN=postgres://user:pass@host:5432/dbname?sslmode=disable
- SQLite example:
- LISTEN_ADDR: TCP address for the SFTP server (default:
0.0.0.0:2022). - HOST_KEY_PATH: Path to SSH host private key file (default:
./data/host_key). If missing, a new RSA key will be generated here on first run. - BASE_FS_ROOT: Base directory under which each user’s root directory is created or enforced (default:
./data/fs). - LOG_PATH: Log file path (default:
./logs/sftp.log). Directory is created if needed. - LOG_LEVEL:
info(default) ordebug.
Example .env:
DB_TYPE=sqlite
DB_DSN=./data/sftp.db
LISTEN_ADDR=0.0.0.0:2022
HOST_KEY_PATH=./data/host_key
BASE_FS_ROOT=./data/fs
LOG_PATH=./logs/sftp.log
LOG_LEVEL=info
On startup, the server checks for a sftp_users table and applies the appropriate DDL file if it’s missing:
- SQLite:
sqlite_ddl.sql - PostgreSQL:
postgres_ddl.sql
Schema fields (abbreviated; see SQL files):
- id (pk), display_name, group_name, username (unique)
- password_hash (bcrypt, nullable if using only key auth)
- public_key (OpenSSH authorized_key string)
- root_path (user’s filesystem root; if empty, defaults to
BASE_FS_ROOT/<username>) - perms (bitmask: 1=Read, 2=List, 4=Write, 8=Delete)
- disabled (bool)
Notes:
- Password auth uses bcrypt.CompareHashAndPassword. Store a bcrypt hash in
password_hash.- You can generate bcrypt hashes with your own tooling or a small Go helper. Ensure you use a reasonable cost.
- Public‑key auth expects the same key material as appears in an
authorized_keysentry (single‑line OpenSSH format). - The server ensures resolved file paths remain inside the user’s root and prevents
..traversal.
- Prepare a
.envfile (see Configuration above). - Ensure data directories exist or let the app create them on startup:
- Host key directory (from
HOST_KEY_PATH) - User base FS directory (
BASE_FS_ROOT) - Log directory (from
LOG_PATH)
- Host key directory (from
Run from source:
- Build:
go build -o v-sftp . - Run:
go run . - Or run the built binary:
./v-sftp(Windows:v-sftp.exe)
The server will listen on LISTEN_ADDR and log to both console and the rotating log file.
Insert user rows into sftp_users. Examples (adjust paths/values as needed):
SQLite (illustrative):
INSERT INTO sftp_users (display_name, group_name, username, password_hash, public_key, root_path, perms, disabled)
VALUES ('Alice Doe', 'default', 'alice', '$2y$...bcrypt-hash...', NULL, 'C:/sftp/alice', 1+2+4+8, 0);
PostgreSQL (illustrative):
INSERT INTO sftp_users (display_name, group_name, username, password_hash, public_key, root_path, perms, disabled)
VALUES ('Bob Doe', 'default', 'bob', '$2y$...bcrypt-hash...', 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... bob@host', '/srv/sftp/bob', 7, FALSE);
Tips:
permsis a bitmask; add the values you need (Read=1, List=2, Write=4, Delete=8). For read+list+write use 1+2+4=7.- If
root_pathis empty, it will default toBASE_FS_ROOT/<username>and be created if missing. - Setting
disableddisables login for that user.
There are no custom scripts in this repository. Useful Go commands:
go mod tidy— ensure dependencies are in syncgo build .— build the servergo run .— run the server from source
- Default log file:
./logs/sftp.log(rotated: max size ~20MB, 7 backups, 14 days, compressed) - Console logs are also emitted. Set
LOG_LEVEL=debugfor more detail.
- There are currently no automated tests in this repository. TODO: add unit tests for path resolution, permission checks, and store operations.
- You can manually verify with any SFTP client (e.g.,
sftp, FileZilla, WinSCP) using a user configured in the DB.
.
├── README.md # This file
├── main.go # Program entry; server setup & SSH/SFTP loop
├── handlers.go # SFTP request handlers (read/write/cmd/list)
├── store.go # User store (SQLite/PostgreSQL) and DDL bootstrap
├── sqlite_ddl.sql # SQLite schema for sftp_users
├── postgres_ddl.sql # PostgreSQL schema for sftp_users
- The server will create an RSA host key if none exists at
HOST_KEY_PATH. For production, manage your host keys securely and with backups. - Always store password hashes (bcrypt), never plaintext passwords.
- Consider running behind a firewall and restricting
LISTEN_ADDRto known interfaces.
MIT License — see LICENSE for details.
- Issues and PRs are welcome. Please include details about your environment and steps to reproduce problems.
- Before submitting changes, run
go buildto ensure the project compiles and consider adding tests where possible.
- None currently.