bolt Valebyte VPS from $4/mo — NVMe, 60s deploy.

Get a VPS arrow_forward
eco Beginner Tutorial/How-to

Installing SFTPGo on a VPS: Secure SFTP Server, Web Panel, and S3 Storage

calendar_month Sep 29, 2026 schedule 20 min read visibility 20 views
Установка SFTPGo на VPS: защищённый SFTP-сервер, веб-панель и S3-хранилище
info

Need a server for this guide? We offer dedicated servers and VPS in 50+ countries with instant setup.

Need a server for this guide?

Deploy a VPS or dedicated server in minutes.

Installing SFTPGo on a VPS: Secure SFTP Server, Web Panel, and S3 Storage

TL;DR

SFTPGo is a self-hosted SFTP server with a web panel, user management, quotas, virtual folders, and support for either a local disk or S3 object storage. In this guide, we will install SFTPGo 2.6.x on Ubuntu Server 24.04 LTS via Docker Compose, secure the administrative panel with a Caddy HTTPS certificate, create a user, and connect an S3 bucket for file storage.

  • OS: Ubuntu Server 24.04 LTS, Docker Engine, and Docker Compose Plugin.
  • SFTPGo: official container image version 2.6.x.
  • Access: SFTP on a separate port, web panel and REST API over HTTPS.
  • Storage: local VPS disk or S3-compatible object storage.
  • Protection: SSH keys, UFW, Fail2ban, TLS, a separate administrator, and regular backups.
  • Maintenance: updating containers while preserving the configuration and database.

1. TL;DR

SFTPGo is suitable when you need your own file portal and SFTP server without being tied to a SaaS provider. The service provides a web interface for administrators and users, supports SFTP, HTTP/S, WebDAV, FTP/S, quotas, and connections to S3-compatible storage.

For a small team, a VPS with 2 vCPU, 4 GB of RAM, and an SSD disk of at least 40 GB is sufficient. If the files themselves will be stored in S3, the local disk is mainly needed for containers, the database, logs, and temporary operations. If a local file system is used, the disk capacity should match the data volume with a reserve of at least 25–30 percent.

  • The administrative interface is not published on a random port: it is exposed externally only through Caddy and HTTPS.
  • SFTP runs on a separate TCP port, for example 2022.
  • SFTPGo data and PostgreSQL are stored in persistent Docker volumes.
  • Database passwords and access keys are not written to the web server configuration.
  • The backup includes the database, configuration, Docker Compose, and user data.

2. Contents

This article is intended for a VPS owner who wants to deploy SFTPGo independently and control access to files. The commands are provided for Ubuntu Server 24.04 LTS with a user who has sudo privileges.

  1. First, we will define the architecture: SFTPGo, PostgreSQL, Caddy, and external S3.
  2. Then we will prepare the server: update the OS, create a separate user, and configure SSH, UFW, and Fail2ban.
  3. After that, we will start the containers and check the logs, ports, and HTTP responses.
  4. In the web panel, we will create an administrator, a regular user, and connect an S3 bucket.
  5. Finally, we will configure backups and the update procedure.

3. What We Are Configuring and Why

What Is SFTPGo

SFTPGo is a server system for secure file exchange. The main protocol in this guide is SFTP over SSH. Unlike a regular Linux system user, an SFTPGo user is managed from the application: you can assign a quota, home directory, virtual folders, upload and download permissions, an account expiration date, and IP restrictions.

SFTPGo has a separate administrative web panel. Users, groups, storage policies, SSH keys, and tokens are created through it. The user web interface allows files to be exchanged through a browser, so installing an SFTP client is not necessary for ordinary document uploads.

Target Architecture

Component Purpose Public Access
SFTPGo Authentication, SFTP, web panel, REST API HTTPS and SFTP port
PostgreSQL Users, groups, settings, and metadata No
Caddy Reverse proxy and automatic TLS certificates 80 and 443
S3 Actual storage of user files Via the S3 API, not directly from the VPS

What the Result Will Be

After completing the instructions, SFTPGo will be available at an address such as https://files.example.com. Users will be able to log in to the web panel or connect from WinSCP, Cyberduck, FileZilla, the Linux command line, and other clients.

The administrative database will be stored in PostgreSQL, while files can be placed in S3. This separates the application from the data: replacing the VPS or container does not need to be combined with migrating a large file array. However, the database and settings still require backups.

Self-Hosted or Cloud Service

Cloud file-sharing services are easier to start using: there is no need to update the OS, monitor TLS, or configure backups. In return, you have to pay a recurring subscription fee, accept plan limitations, and transfer your data to an external operator.

A self-hosted VPS solution is justified if you need control over data location, your own access policy, integration with S3, LDAP, or internal systems. The server owner is responsible for updates and backups, but can independently choose the protocols, limits, and storage architecture.

When SFTPGo Is Not the Best Choice

If you only need to transfer a few files once, a full-fledged service will be excessive. Nextcloud, Syncthing, or specialized backup tools may be better suited for synchronizing working directories between laptops. SFTPGo is especially useful where managed accounts, SFTP compatibility, permission restrictions, and access to object storage are required.

4. What VPS Configuration Is Needed for This Task

Minimum Configuration

SFTPGo does not require a large amount of CPU for one administrator and several users. The main load depends on the number of simultaneous connections, TLS termination, storage speed, and file size. For a test installation, 1 vCPU and 2 GB of RAM are sufficient, but for continuous operation it is better to start with 2 vCPU and 4 GB of RAM.

Scenario CPU RAM Disk Network
Testing 1 vCPU 2 GB 20–30 GB SSD 100 Mbps
Small team 2 vCPU 4 GB 40–80 GB SSD 500 Mbps or higher
Many users 4–8 vCPU 8–16 GB 80 GB plus cache capacity 1 Gbps

If the files are stored in S3, you do not need to purchase a disk as large as the entire archive. However, extra disk space is still needed for PostgreSQL, Docker logs, temporary files, backups, and updates. A practical basic option is 2 vCPU, 4 GB of RAM, a 60 GB NVMe SSD, and a public IPv4 address. For this configuration, you can choose a suitable VPS with Linux and full root access.

Network Requirements

You need an inbound IPv4 address, and preferably IPv6 as well. The DNS name files.example.com must point to the server address. For Caddy to automatically obtain a TLS certificate, TCP ports 80 and 443 must be open. A separate port, such as 2022, will be used for SFTP.

Check your provider’s restrictions on outbound connections to S3. Some networks block certain ranges or limit the number of connections. When working with large files, not only port speed matters, but also the stability of the route to the S3 region.

When a Dedicated Server Is Needed

A dedicated server is justified if the local storage volume exceeds several terabytes, several fast NVMe drives are needed, there are dozens or hundreds of simultaneous uploads, or strict isolation from neighboring virtual machines is required. For SFTPGo using external S3, switching to a dedicated server is usually not the first scaling step.

First, measure CPU, RAM, I/O, and network bandwidth. If only storage becomes the bottleneck, it is more cost-effective to move the data to S3 or connect a separate storage server. A dedicated server makes sense when predictable performance of all resources is required simultaneously.

Choosing a Location

Place the VPS closer to the main users and the S3 region. This reduces latency when working with the web panel and transferring files via SFTP. If the data is subject to legal requirements or company policy, the region of the server and object storage must comply with those requirements.

5. Server Preparation

System Update and Hostname

The following assumes a clean Ubuntu Server 24.04 LTS installation. Connect as a temporary user with sudo privileges or as root, set the hostname, and update the packages.

# Задаём имя сервера
sudo hostnamectl set-hostname sftp-01

# Обновляем список пакетов и устанавливаем обновления
sudo apt update && sudo apt full-upgrade -y

# Перезагружаем сервер, если обновлялось ядро
sudo reboot

Connect via SSH again after the reboot. Create a separate user for administration. Use your own name instead of deploy.

# Создаём обычного пользователя
sudo adduser deploy

# Добавляем его в группу sudo
sudo usermod -aG sudo deploy

# Проверяем принадлежность к группам
id deploy

SSH Keys

On the local computer, generate an Ed25519 key if you do not already have one. Do not transfer the private key to the server or store it in the Docker project.

# Выполняется на вашем локальном компьютере
ssh-keygen -t ed25519 -C "deploy@sftp-01"

# Копируем публичный ключ на сервер
ssh-copy-id deploy@SERVER_IP

Test logging in through a new session without closing the current root session. Only after a successful check can password-based login be disabled.

# Открываем конфигурацию SSH
sudoedit /etc/ssh/sshd_config.d/99-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
MaxAuthTries 3
# Проверяем синтаксис и перечитываем конфигурацию SSH
sudo sshd -t && sudo systemctl reload ssh

Firewall

Allow the SSH port before enabling UFW. In this example, SSH runs on the standard port 22. If you have changed it, substitute your own value.

# Устанавливаем UFW и разрешаем SSH, HTTP и HTTPS
sudo apt install -y ufw
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Отдельный порт SFTPGo
sudo ufw allow 2022/tcp

# Включаем firewall с политикой deny incoming
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

# Проверяем активные правила
sudo ufw status verbose

Fail2ban and Basic Utilities

Fail2ban analyzes failed login attempts in the logs and temporarily blocks IP addresses. It does not replace SSH keys, MFA, or a firewall, but it reduces automated brute-force attempts.

# Устанавливаем защиту SSH и служебные инструменты
sudo apt install -y fail2ban ca-certificates curl wget git jq unzip \
    htop ncdu unattended-upgrades apt-transport-https

# Включаем Fail2ban при запуске
sudo systemctl enable --now fail2ban

# Создаём локальную настройку jail для SSH
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
port = 22
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
EOF

# Перезапускаем Fail2ban и проверяем статус
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

If SSH runs on another port, change the port parameter in the jail and the UFW rule. Do not use changing the port as the only protection method: keys, disabling root, and limiting attempts are more important.

6. Software Installation — Step by Step

Step 1. Installing Docker from the Official Repository

For reproducible deployment, use Docker Engine and Compose Plugin from the official Docker apt repository. At the time of preparing these instructions, for production use the current stable Docker Engine 27.x or a newer compatible release available in the repository.

# Remove conflicting old packages
sudo apt remove -y docker.io docker-compose docker-doc podman-docker containerd runc || true

# Add the official Docker repository key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo tee /etc/apt/keyrings/docker.asc > /dev/null
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Connect the repository for the current Ubuntu release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine and Compose Plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
    docker-buildx-plugin docker-compose-plugin

# Enable Docker at startup
sudo systemctl enable --now docker

# Check versions
sudo docker version
sudo docker compose version

Step 2. Allowing the deploy User to Access Docker

Adding a user to the docker group allows Docker to be run without sudo, but effectively grants root-level privileges. Do this only for a trusted administrator.

# Add deploy to the Docker group
sudo usermod -aG docker deploy

# Update the group in the current shell session
newgrp docker

# Check running without sudo
docker run --rm hello-world

Step 3. Creating the Project Structure

Create a separate directory. The .env file will contain secrets and will remain only on the server.

# Create the application directory
sudo mkdir -p /opt/sftpgo
sudo chown -R deploy:deploy /opt/sftpgo
cd /opt/sftpgo

# Create the secrets file with restricted permissions
touch .env
chmod 600 .env

# Check the current directory
pwd

Step 4. Environment Secrets

Generate long, random passwords. The PostgreSQL password must not match the SFTPGo administrator password. The SFTPGO_DEFAULT_ADMIN_PASSWORD variable is used only during the initial initialization, so save it in a password manager.

# Generate random values
DB_PASSWORD=$(openssl rand -hex 32)
ADMIN_PASSWORD=$(openssl rand -base64 30 | tr -dc 'A-Za-z0-9!@#%+=' | head -c 28)

# Write the variables to .env
cat > .env <<EOF
POSTGRES_DB=sftpgo
POSTGRES_USER=sftpgo
POSTGRES_PASSWORD=${DB_PASSWORD}
SFTPGO_ADMIN_USERNAME=admin
SFTPGO_ADMIN_PASSWORD=${ADMIN_PASSWORD}
EOF

# Show only variable names, not values
cut -d= -f1 .env

Step 5. Docker Compose

PostgreSQL 16 and the official SFTPGo image are used below. Replace the v2.6.x tag with the specific latest stable 2.6 patch release verified before installation. Do not use the constantly floating latest tag in a critical system without testing.

# Create the Docker Compose file
cat > compose.yml <<'EOF'
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file:
      - .env
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  sftpgo:
    image: drakkan/sftpgo:v2.6.x
    restart: unless-stopped
    env_file:
      - .env
    environment:
      SFTPGO_DATA_PROVIDER__DRIVER: postgresql
      SFTPGO_DATA_PROVIDER__NAME: ${POSTGRES_DB}
      SFTPGO_DATA_PROVIDER__HOST: postgres
      SFTPGO_DATA_PROVIDER__PORT: 5432
      SFTPGO_DATA_PROVIDER__USERNAME: ${POSTGRES_USER}
      SFTPGO_DATA_PROVIDER__PASSWORD: ${POSTGRES_PASSWORD}
      SFTPGO_DEFAULT_ADMIN_USERNAME: ${SFTPGO_ADMIN_USERNAME}
      SFTPGO_DEFAULT_ADMIN_PASSWORD: ${SFTPGO_ADMIN_PASSWORD}
      SFTPGO_SFTPD__BINDINGS__0__PORT: 2022
      SFTPGO_HTTPD__BINDINGS__0__PORT: 8080
      SFTPGO_HTTPD__BINDINGS__0__BIND_ADDRESS: 0.0.0.0
    ports:
      - "2022:2022"
      - "127.0.0.1:8080:8080"
    volumes:
      - sftpgo_data:/var/lib/sftpgo
      - sftpgo_home:/srv/sftpgo
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - backend

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - sftpgo
    networks:
      - backend

volumes:
  postgres_data:
  sftpgo_data:
  sftpgo_home:
  caddy_data:
  caddy_config:

networks:
  backend:
EOF

Two persistent SFTPGo volumes are used in Compose. The first stores the application's configuration and internal data; the second is intended for users' local home directories. Even if the primary backend is S3, these volumes must not be deleted without a backup.

Step 6. Starting the Database and SFTPGo

# Check the final Compose configuration
docker compose config

# Pull images and start PostgreSQL and SFTPGo
docker compose up -d postgres sftpgo

# Check the container status
docker compose ps

# View the SFTPGo startup log
docker compose logs --tail=100 sftpgo

On the first launch, SFTPGo creates the PostgreSQL tables and the administrator user. If the container restarts, first check the database log: the most common causes are incorrect connection variables, a corrupted volume, or the application starting before PostgreSQL is ready.

Step 7. Installing Caddy

Caddy will run in a separate container and proxy the SFTPGo web interface. SFTP connections do not pass through Caddy: port 2022 is published directly by the SFTPGo container.

# Create a Caddyfile with the domain name
cat > Caddyfile <<'EOF'
files.example.com {
    reverse_proxy sftpgo:8080

    header {
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        X-Frame-Options "SAMEORIGIN"
    }

    encode zstd gzip
}
EOF

# Start the reverse proxy
docker compose up -d caddy

# Check all services
docker compose ps

Replace files.example.com with your own DNS name before starting Caddy. If DNS has not propagated yet, the container will run, but it will not be able to obtain an automatic certificate.

7. Configuration

Checking DNS, HTTPS, and SFTP

First, make sure that the domain resolves to the correct address. Perform the check from a local computer or another machine on the internet.

# Check the DNS record
dig +short files.example.com

# Check HTTPS and the redirect chain
curl -I https://files.example.com

# Check the SFTP port
nc -vz files.example.com 2022

# Check the local HTTP port on the server
curl -I http://127.0.0.1:8080

The expected HTTPS response is 200, 302, or another application response without a TLS error. The nc command should show a successful TCP connection. If the port is accessible locally but not externally, check UFW and the network firewall rules in the VPS control panel.

First Login to the Panel

Open https://files.example.com and log in using the username and password from the .env file. Immediately change the initial password in the interface and save it in a password manager. Do not use the administrator account for daily file transfers.

In the panel, check the HTTP, SFTP, users, groups, and logs settings sections. If you later change the port or binding through the web interface, note that the Compose environment variables are applied when the container is created and may be overridden by the application settings.

Creating an SFTP User

  1. Open the users section and create a new account.
  2. Set a unique username and a long password, or add an SSH public key.
  3. Specify a home directory or virtual folder.
  4. Restrict permissions: list, download, and upload are usually sufficient.
  5. Set the disk space and file count quotas.
  6. If necessary, restrict the allowed IP addresses.
  7. Save the user and perform a test connection.

For service integrations, a separate user with minimal permissions and an SSH key is preferable. Do not share one password among multiple employees: in the event of termination or a leak, it will be impossible to determine the exact source of access.

Connecting S3 Storage

SFTPGo allows you to assign an object storage backend to a user or virtual folder. Amazon S3 and many compatible APIs are supported, including storage providers with a custom endpoint. In the user creation panel, open the file system settings and select the S3 storage type.

Fill in the fields according to the following scheme:

Field Example Comment
Bucket company-files-prod Bucket name without a URL prefix
Region eu-central-1 The region in which the bucket was created
Endpoint https://s3.example-storage.com Required for an S3-compatible provider; may be empty for AWS
Access key separate application key Do not use the account master key
Secret key secret key Store only in the panel and secrets manager
Key prefix team-a/ Isolates one user's files in the bucket

Create a separate S3 user with access only to the required bucket and prefix. The minimum set of permissions usually includes reading, uploading, and deleting objects, as well as viewing the bucket contents. Some providers additionally require access to multipart upload.

Do not expose the S3 bucket directly to the internet if files should be provided only through SFTPGo. Disable public access, enable storage-side encryption, and configure a lifecycle policy for old versions or incomplete multipart uploads.

Testing SFTP

Create a test file and connect to the server. On Linux or macOS, you can use the built-in OpenSSH client.

# Connect to SFTPGo using a regular password
sftp -P 2022 [email protected]

# Run SFTP commands after logging in
pwd
ls
put test.txt
get test.txt
exit

To connect using a key, specify the client's private key:

# Connect using an SSH key
sftp -i ~/.ssh/sftp_user_ed25519 -P 2022 [email protected]

Verify that the user cannot leave the assigned virtual space, read other users' directories, or execute shell commands. SFTPGo does not provide regular shell access, but the final permissions should still be checked using a separate test account.

Log Monitoring and Healthcheck

# View application logs in real time
docker compose logs -f --tail=100 sftpgo

# View reverse proxy errors
docker compose logs --tail=100 caddy

# Check whether containers are restarting
docker compose ps

# View disk and memory usage
df -h
free -h
docker system df

For automated monitoring, add checks for HTTPS, TCP port 2022 availability, and free disk space. The external monitoring system should alert you if the HTTP response does not arrive for several minutes or disk usage exceeds 80 percent.

Panel Security

The administrator panel must be accessible only over HTTPS. Do not expose port 8080 to the internet: in Compose, it is bound to 127.0.0.1, so it is accessible only on the server itself and from the Docker network.

Use MFA if the feature is available in your version and selected authentication method. Restrict access to the REST API separately, do not store API tokens in Git, and revoke them after the integration is complete. Administrative logs should be reviewed after changes to users and permissions.

8. Backups and maintenance

What needs to be backed up

The minimum backup set consists of PostgreSQL, the compose.yml file, Caddyfile, the environment file or its encrypted copy, as well as SFTPGo Docker volumes. If files are stored on a local disk, user data must also be copied. With S3 storage, the objects themselves are already outside the VPS, but the bucket configuration, permissions, lifecycle policy, and versioning should be backed up.

Object Frequency Recommended retention
PostgreSQL dump Daily 14–30 days
Compose and Caddy configuration After every change All versions for 90 days
Local user files Daily or according to RPO According to business requirements
S3 objects Versioning and lifecycle According to the retention policy

Simple backup with restic

The example below uses restic and an external S3-compatible repository. The database backup is first created using pg_dump, after which the project directory is archived in restic. The restic password and S3 keys should be stored in a separate protected file inaccessible to other users.

# Устанавливаем restic
sudo apt install -y restic

# Создаём файл переменных резервного копирования
sudo install -m 600 /dev/null /root/.restic-env
sudoedit /root/.restic-env
export AWS_ACCESS_KEY_ID="backup-access-key"
export AWS_SECRET_ACCESS_KEY="backup-secret-key"
export RESTIC_REPOSITORY="s3:https://backup-s3.example.com/sftpgo-repo"
export RESTIC_PASSWORD="длинный-пароль-restic"

Create the script. The docker compose exec command performs the dump inside the PostgreSQL container and saves it to the mounted project directory.

# Создаём каталог для временных дампов
sudo mkdir -p /var/backups/sftpgo
sudo chmod 700 /var/backups/sftpgo

# Создаём скрипт резервного копирования
sudo tee /usr/local/sbin/backup-sftpgo.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail

cd /opt/sftpgo
source /root/.restic-env

STAMP="$(date +%F-%H%M%S)"
DUMP="/var/backups/sftpgo/postgres-${STAMP}.sql.gz"

docker compose exec -T postgres \
  pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" | gzip > "$DUMP"

restic backup \
  "$DUMP" \
  /opt/sftpgo/compose.yml \
  /opt/sftpgo/Caddyfile \
  /opt/sftpgo/.env

restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

find /var/backups/sftpgo -type f -name '.sql.gz' -mtime +3 -delete
EOF

# Делаем скрипт исполняемым
sudo chmod 700 /usr/local/sbin/backup-sftpgo.sh

# Инициализируем restic repository один раз
sudo bash -c 'source /root/.restic-env && restic init'

# Выполняем тестовый backup
sudo /usr/local/sbin/backup-sftpgo.sh

To run it on a schedule, add a cron job for root. A nightly backup is only an example; choose a time that takes user activity and RPO requirements into account.

# Открываем cron root
sudo crontab -e
30 02    /usr/local/sbin/backup-sftpgo.sh >> /var/log/backup-sftpgo.log 2>&1

Once a month, perform a test restoration to a separate directory or test VPS. A backup that has never been tested through restoration cannot be considered reliable.

Updating SFTPGo

Before updating, record the current image tag and perform a full backup. For a small installation, use a maintenance window: stop the services, download the new image, start the containers, and verify functionality.

# Переходим в каталог проекта
cd /opt/sftpgo

# Создаём резервную копию перед обновлением
sudo /usr/local/sbin/backup-sftpgo.sh

# Загружаем новую проверенную версию образов
docker compose pull

# Перезапускаем контейнеры без удаления volumes
docker compose up -d

# Проверяем состояние и последние ошибки
docker compose ps
docker compose logs --tail=100 sftpgo

Do not run docker compose down -v: the -v option removes named volumes and may destroy the database and local data. Update the test copy first, then production. For a large installation, use blue-green deployment or a separate standby server, but database migrations still require version compatibility.

Resource monitoring

Monitor filesystem usage, inodes, memory, and Docker log size. Limit Docker log rotation; otherwise, prolonged service operation may fill the disk.

# Проверяем inode и свободное место
df -h
df -i

# Проверяем потребление контейнеров
docker stats --no-stream

# Ищем крупные каталоги
sudo du -xhd1 /var/lib/docker /opt /var/log 2>/dev/null | sort -h

9. Troubleshooting and FAQ

Why does HTTPS show a certificate error?

Verify that the DNS name points to the server’s public IP and that TCP ports 80 and 443 are allowed in UFW and the external firewall. Check the log with docker compose logs caddy: it will contain the reason for the ACME failure. Make sure another web server has not occupied ports 80 or 443. If a DNS proxy is used, temporarily verify that the A/AAAA records are correct and that the origin server is accessible.

The SFTPGo container keeps restarting. What should I check?

Start with docker compose logs --tail=200 sftpgo and docker compose ps. Common causes include an incorrect PostgreSQL variable name, an incorrect password in .env, an unavailable database service, or a corrupted volume. Check PostgreSQL’s status using docker compose logs postgres. The docker compose config command displays the final values and YAML errors, but do not output its result to public logs: it may contain secrets.

What is the minimum suitable VPS configuration?

For testing, 1 vCPU, 2 GB of RAM, and 20–30 GB of SSD will be sufficient. For continuous operation by a small team, it is better to use 2 vCPUs, 4 GB of RAM, and 40–60 GB of NVMe. If files are stored in S3, the disk does not need to match the archive size, but room is needed for the database, logs, temporary files, and backups. With local storage, add the volume of user data and at least 25 percent free space.

Which should you choose for this task — a VPS or dedicated server?

In most cases, a VPS is sufficient: SFTPGo and PostgreSQL use a moderate amount of CPU, and data can be moved to S3. A dedicated server is needed for a large local file archive, high sustained disk load, a large number of simultaneous transfers, or physical isolation requirements. Measure resource usage through monitoring before switching. It is often cheaper to scale the disk or S3 than to rent a dedicated server immediately.

Port 2022 is open, but SFTP does not connect

Check that the container is published with the 2022:2022 rule and that SFTPGo is actually listening on this port: docker compose logs sftpgo and sudo ss -lntp | grep 2022. Then check UFW and the external firewall. The client must use the SFTP protocol, not FTP or FTPS. Also check the login, user status, account expiration date, and permitted IP list.

The user logs in but cannot see files in S3

First check the bucket, region, and endpoint. For an S3-compatible endpoint, you usually need to specify the full URL with HTTPS. Check the key permissions: at minimum, viewing, reading, and uploading operations are required; deletion requires a separate delete permission. Make sure the prefix contains no extra spaces or leading slash. Check the SFTPGo log during the operation: AWS SDK errors usually indicate an incorrect region, signature, ACL, or missing permission.

Users and settings disappeared after a restart

Check that PostgreSQL and SFTPGo use the named volumes specified in Compose. Do not start the project from another directory with a different Compose file and do not run docker compose down -v. The docker volume ls and docker volume inspect commands show the existing volumes. If the database was deleted, restore it from pg_dump and only then start SFTPGo.

How can access to the administration panel be restricted?

Keep the SFTPGo HTTP port bound to 127.0.0.1, as in the example, and publish the interface only through Caddy. You can additionally restrict access to the domain at the VPN, reverse proxy, or network firewall level. Use separate administrator accounts, MFA, and short-lived API tokens. Do not expose the panel on a directly public port without TLS.

Can files be stored only on the VPS?

Yes. Create a user with a local filesystem and specify a home directory inside the sftpgo_home volume or a separate bind mount. This option is simpler and faster on a local network, but it requires disk monitoring and a separate data copy. Do not consider a Docker volume a backup: disk failure or VPS deletion will destroy it along with the container. For production, use an external backup or replication.

10. Conclusions and next steps

As a result, we obtained SFTPGo on a VPS with PostgreSQL, HTTPS through Caddy, a separate SFTP port, user management, and the ability to store files in S3. The system separates application metadata from files and allows storage to be scaled independently of computing resources.

The next practical step is to enable MFA, configure external monitoring, and regularly test restoration from backup. As the load grows, measure CPU, memory, network traffic, and S3 latency, then choose between increasing the VPS capacity, using a separate storage server, and deploying a dedicated server.

  • Create separate user groups with minimal permissions and quotas.
  • Enable versioning and a lifecycle policy in S3 to protect against accidental deletion.
  • Document the restoration procedure on a clean server and test it after every major update.

Was this guide helpful?

Your feedback helps us improve our guides.

Share this post:

Send this guide to someone who may find it useful.

Telegram VKVK WhatsApp Facebook LinkedIn XX

installing SFTPGo on a VPS: secure SFTP server, web panel, and S3 storage
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.