Operations

Self-Host S3-Compatible Object Storage with MinIO on a VPS

The first thing you notice when you run out of disk on a VPS is that copying files to another server or to cloud storage becomes a messy pipeline of rsync, cron, and hope. Object storage, the S3 model, would fix that: a single API endpoint, buckets, metadata, and no local mount point. But sending your production data to a provider in another region adds latency, egress cost, and a dependency you cannot control. MinIO gives you the same S3 protocol on your own Linux VPS, running in minutes, with the same CLI and SDK compatibility AWS S3 uses. This post walks through a production-grade MinIO deployment, from binary install to systemd, nginx reverse proxy with TLS, bucket creation, and a client test, so you own your object storage from day one.

  • Key takeaways: MinIO speaks the AWS S3 API natively, use aws-cli, boto3, or any S3 SDK. Set up systemd for automatic restart and logging. Put nginx in front with a Let's Encrypt TLS certificate so connections are encrypted. Test with mc (MinIO Client) to verify everything works before moving data in.

Prerequisites

  • A Linux VPS running Ubuntu 24.04 or Debian 12 (this guide uses Ubuntu 24.04).
  • A non-root sudo user.
  • A domain name (for example s3.yourdomain.com) pointing to your VPS IP.
  • Port 443 open in the firewall; port 9000 open only for internal or direct test access.
  • At least 2 GB of RAM and 20 GB of NVMe disk, 4 GB / 40 GB is better if you plan to store real data.

越南 VPS 自建 MinIO 对象存储,完全兼容 S3 协议,适合本地部署备份与应用数据。

A Vietnam VPS running MinIO gives you local S3-compatible object storage for backups and application data, fully under your own control.

Why Run MinIO on Your Own VPS Instead of Cloud S3

Cloud S3 costs add up: egress bandwidth, per-request fees, and the locked-in SDK. For a small application or a handful of services inside your own VPC (or even across a few VPS instances), a self-hosted MinIO server removes all metering. You pay for the VPS, once, and the disk is your limit. MinIO runs as a single binary (or Docker container), weighs under 100 MB, and uses the same bucket/policy/versioning API that AWS S3 uses, so any code written for S3 works unmodified.

MinIO also gives you features AWS charges extra for: server-side encryption with your own keys, bucket lifecycle rules, and object locking for write-once-read-many (WORM) scenarios. And because it sits on your own VPS with a dedicated IPv4, latency is as low as your network stack, no DNS round‑trips to us-east-1.

Step 1, Download and Install the MinIO Server Binary

MinIO distributes a single static binary. No dependencies, no pip install. We install into /usr/local/bin and create a system user to run it.

sudo apt update
sudo apt install -y wget gnupg
cd /tmp
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/
minio --version   # verify, should print version "RELEASE.2025-*" or later

Then create the user and data directory:

sudo useradd -r -s /usr/sbin/nologin minio-user
sudo mkdir -p /data/minio
sudo chown minio-user:minio-user /data/minio

Verify: run which minio, the output must be /usr/local/bin/minio.

Step 2, Environment Configuration for Production

MinIO uses environment variables for credentials and data paths. Write them into a file that systemd will source. The root user and password are for the initial admin account, change them to a strong random string.

sudo mkdir -p /etc/minio
sudo nano /etc/minio/minio.conf

Paste the following (replace MINIO_ROOT_USER and MINIO_ROOT_PASSWORD with strong values):

MINIO_ROOT_USER=yourminioadmin
MINIO_ROOT_PASSWORD=StrongPassw0rd!ChangeMe
MINIO_VOLUMES=/data/minio
MINIO_OPTS="--address :9000 --console-address :9001"

The console port :9001 is the web UI. Do not expose it directly to the internet, we will put nginx in front of the API port (:9000).

Set permissions so only root can read the credentials:

sudo chown root:root /etc/minio/minio.conf
sudo chmod 600 /etc/minio/minio.conf

Step 3, Run MinIO as a systemd Service

A systemd unit keeps MinIO running across reboots, logs via journald, and supports systemctl start/stop/restart as you would expect.

sudo nano /etc/systemd/system/minio.service

Paste:

[Unit]
Description=MinIO Object Storage
Documentation=https://min.io/docs/minio/linux/index.html
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/minio/minio.conf
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
RestartSec=5
User=minio-user
Group=minio-user
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable minio
sudo systemctl start minio
sudo systemctl status minio

Verify: you should see Active: active (running) and a line stating "API: http://...:9000" in the logs. Use sudo journalctl -u minio --no-pager -n 20 to confirm the service started cleanly.

Step 4, Set Up nginx as a Reverse Proxy with TLS

Exposing MinIO API directly on port 9000 is fine for a private LAN, but from the internet you want TLS termination and a clean domain. Install nginx and Certbot:

sudo apt install -y nginx certbot python3-certbot-nginx
sudo nginx -t

Then create an nginx site for your domain (replace s3.yourdomain.com with your real domain):

sudo nano /etc/nginx/sites-available/s3

Paste:

server {
    listen 80;
    server_name s3.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name s3.yourdomain.com;

    # Certbot will fill these paths
    ssl_certificate /etc/letsencrypt/live/s3.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/s3.yourdomain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:9000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # Increase max body size for large uploads
        client_max_body_size 0;
    }
}

Enable the site and request the certificate:

sudo ln -s /etc/nginx/sites-available/s3 /etc/nginx/sites-enabled/
sudo certbot --nginx -d s3.yourdomain.com
sudo systemctl reload nginx

Verify: curl -I https://s3.yourdomain.com should return HTTP/2 403 (access denied, that means nginx and TLS work; MinIO is listening on the other side). A 200 would also mean a valid response; a 403 is normal when no credentials are sent.

Step 5, Configure the Firewall

With nginx on port 443, you can close port 9000 to the outside. Use ufw (Ubuntu) or firewalld (AlmaLinux/Rocky). For ufw:

sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
sudo ufw deny 9000/tcp       # block direct access from outside
sudo ufw reload

Verify: sudo ufw status verbose should show 443/tcp ALLOW and 9000/tcp DENY.

Step 6, Access the MinIO Console and Create a Bucket

Open a browser at https://s3.yourdomain.com, you will see the MinIO login page. Log in with the credentials you set in /etc/minio/minio.conf. After the first login, MinIO may ask you to change the root password (recommended).

Create a bucket: click the red "+" in the top‑right corner → "Create Bucket" → name it (e.g. myapp-assets). The bucket name must be globally unique within your MinIO instance (lowercase, no underscores).

For programmatic access, generate a pair of Access Key and Secret Key: click the user icon → "Identity" → "Users" → create a new user or generate a service account for the root user. These keys work exactly like AWS access keys against any S3 SDK.

You can also do everything from the command line. Install the MinIO client (mc):

wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
mc alias set myminio https://s3.yourdomain.com yourminioadmin StrongPassw0rd!ChangeMe
mc mb myminio/backups
mc ls myminio/backups

Verify: the last command should print nothing (the bucket is empty), no errors means the connection, TLS, and authentication work.

Step 7, Test with AWS CLI

Because MinIO is S3‑compatible, you can use the standard aws CLI to write objects:

pip install awscli
aws configure set aws_access_key_id yourminioadmin
aws configure set aws_secret_access_key StrongPassw0rd!ChangeMe
aws configure set region us-east-1   # MinIO ignores region, but aws-cli needs one

aws --endpoint-url https://s3.yourdomain.com s3 ls
aws --endpoint-url https://s3.yourdomain.com s3 cp /etc/hostname s3://backups/hostname-test
aws --endpoint-url https://s3.yourdomain.com s3 ls s3://backups/

Verify: you should see hostname-test in the output. Delete the test object afterwards.

Troubleshooting

MinIO service fails to start

Check the journal: sudo journalctl -u minio --no-pager -n 50. Common causes: the data directory does not exist or has wrong ownership (chown it to minio-user), or the environment file path is wrong. Verify EnvironmentFile matches /etc/minio/minio.conf.

nginx returns 502 Bad Gateway

Usually means nginx cannot reach MinIO. Confirm MinIO is listening on 127.0.0.1:9000: sudo ss -tlnp | grep 9000 should show minio. If it shows 0.0.0.0:9000 instead of 127.0.0.1, your MinIO is bound to all interfaces, this is acceptable with the firewall closed, but ideally pass --address 127.0.0.1:9000 in MINIO_OPTS for tighter security.

Uploads fail with "Request Entity Too Large"

nginx's default client_max_body_size is 1 MB. We set it to 0 (no limit) in the config above, if you skipped that line, add it and reload nginx.

FAQ

What is MinIO used for?

MinIO is a high‑performance, S3‑compatible object storage server. It stores unstructured data (images, videos, backups, logs) as objects inside buckets and exposes an API identical to AWS S3, so any S3 client works against it without modifications.

Is MinIO free to use in production?

MinIO Community Edition (the version in this guide) is free and open source under the AGPLv3 license. As of early 2026, MinIO has introduced a commercial product called MinIO AIStor, but the CE binary remains available and maintained for self‑managed deployments. Always check the official license at min.io for the latest terms.

Can I run MinIO and my application on the same VPS?

Yes, if the VPS has enough RAM and disk. MinIO is lightweight, it uses about 200 MB idle and scales up with object count. For a small application (under 50 GB of data), a 4 GB RAM, 80 GB NVMe VPS is sufficient. Separate the data directory from the OS root to avoid filling up the system disk.

How do I enable TLS without nginx?

MinIO supports native TLS if you place a certificate and key at ~/.minio/certs/public.crt and ~/.minio/certs/private.key for the minio-user. This guide uses nginx as a reverse proxy because it simplifies certificate management with Certbot and keeps the MinIO configuration minimal.

Can I access MinIO from another VPS or a Docker container on the same network?

Absolutely, point your S3 SDK or MinIO client at the domain name. If both servers are on the same private network inside the same datacenter, you can bind MinIO to the private IP and avoid the public internet entirely. Use the same --address option with a private IP.

Related articles

越南 VPS 搭建 MinIO S3 兼容对象存储

本文完整演示了在越南 Linux VPS 上部署 MinIO S3 对象存储的步骤:下载二进制文件、配置 systemd 服务、设置 nginx 反向代理并获取 Let's Encrypt TLS 证书、创建存储桶并通过 aws-cli 验证。MinIO 兼容 AWS S3 API,可替代云厂商的 S3 服务,适用于备份、媒体文件和应用数据存储。使用越南本地 VPS 部署后,数据延迟更低,且无需承担云存储的出站流量费用。

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.