为什么要在 VPS 上自建对象存储?
越来越多的开发者和中小企业在日常工作中依赖对象存储——无论是网站的图片托管、应用的上传文件归档,还是数据备份与分发。AWS S3、阿里云 OSS、腾讯云 COS 等公有云服务虽然稳定,但费用随着流量和存储量线性增长,尤其当你需要对外提供大量公开读取的文件时(比如图床、静态资源 CDN 回源),带宽费用往往远超存储费用。
一台月费 5~10 美元的 VPS,搭配开源的 MinIO 对象存储引擎,就能轻松替代公有云 S3 的核心功能,而且数据完全在自己手中,没有供应商锁定,也没有流量出口的额外计费。本文将手把手带你完成 MinIO 在 VPS 上的生产级部署,包括安全加固、域名配置、客户端工具使用,以及与常用应用(WordPress、图床)的集成方案。
MinIO 是什么?和 S3 有什么关系?
MinIO 是一款用 Go 语言编写的高性能对象存储服务器,完全兼容 Amazon S3 API。这意味着所有为 S3 开发的 SDK、CLI 工具和应用程序,只要把 endpoint 指向你的 MinIO 服务器,就能无缝切换——代码几乎不用改。
MinIO vs 公有云 S3 核心对比
| 维度 | AWS S3 / 阿里云 OSS | MinIO 自建 |
|---|---|---|
| 存储成本 | $0.023/GB/月 | VPS 磁盘自带,零额外费用 |
| 流量费用 | $0.09/GB(公网下载) | VPS 带宽包月,无按量计费 |
| 数据主权 | 数据在云厂商机房 | 数据完全自控 |
| API 兼容性 | 原生 S3 | S3 完全兼容 |
| 高可用 | 跨可用区冗余 | 单节点需自行备份,分布式需多机 |
| 运维成本 | 零运维 | 需自行维护服务 |
如果你的使用场景是单站点或中小规模(几十 GB 到几百 GB),一台 VPS + MinIO 是性价比极高的选择。大型企业或需要 99.999% 可用性的场景,仍然建议使用公有云。
环境准备与系统选型
推荐使用 Debian 12 或 Ubuntu 22.04+ 作为操作系统。MinIO 对系统要求极低:官方最低配置仅为 1 核 CPU、512MB 内存。但考虑到你可能同时运行 Nginx 等反向代理,建议至少 1 核 1GB 内存的 VPS。
磁盘是对象存储的核心。强烈建议使用 SSD 或 NVMe 硬盘的 VPS 方案,HDD 在高并发小文件场景下性能差距明显。存储目录建议挂载独立的数据盘,方便扩容和备份。
基础环境检查
1
2
3
4
5
6
7
8
9
10
11
12 # 确认系统版本
lsb_release -a
# 检查磁盘空间
df -h
# 创建数据目录
mkdir -p /data/minio
# 创建专用系统用户(安全最佳实践)
useradd -r -s /sbin/nologin minio-user
chown -R minio-user:minio-user /data/minio
安装 MinIO 服务端
MinIO 官方提供单个二进制文件,没有复杂的依赖,安装非常简单。以下使用官方推荐的稳定版本下载方式:
1
2
3
4
5
6
7
8
9
10 # 下载 MinIO 服务端(Linux amd64)
wget https://dl.min.io/server/minio/release/linux-amd64/minio \
-O /usr/local/bin/minio
# 赋予执行权限
chmod +x /usr/local/bin/minio
# 验证版本
minio --version
# 输出示例:minio version RELEASE.2024-11-07T00-52-20Z
同时也安装官方客户端工具 mc(MinIO Client),后续配置 Bucket 和上传文件都需要它:
1
2
3
4
5
6 # 下载 mc 客户端
wget https://dl.min.io/client/mc/release/linux-amd64/mc \
-O /usr/local/bin/mc
chmod +x /usr/local/bin/mc
mc --version
配置 systemd 服务与安全启动
生产环境务必使用 systemd 管理 MinIO 进程,确保开机自启和异常自动重启。关键是使用非 root 用户运行,并将敏感凭证放在环境文件中。
创建环境变量文件
1
2
3
4
5
6
7
8
9
10
11
12
13 # /etc/default/minio — MinIO 环境配置
# 服务端监听地址(仅本地,由 Nginx 反代)
MINIO_OPTS="--address 127.0.0.1:9000 --console-address 127.0.0.1:9001"
# 管理员账号(务必修改为强密码)
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=YourStrongPassword123!@#
# 数据目录
MINIO_VOLUMES="/data/minio"
# 区域标识(可选)
MINIO_SERVER_URL="https://s3.yourdomain.com"
将上述内容写入
1 | /etc/default/minio |
,并设置严格权限:
1
2 chmod 600 /etc/default/minio
chown root:minio-user /etc/default/minio
创建 systemd 服务单元
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 # /etc/systemd/system/minio.service
[Unit]
Description=MinIO Object Storage
Documentation=https://min.io/docs/minio/linux
After=network.target
[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
RestartSec=10
LimitNOFILE=65536
TasksMax=infinity
TimeoutStopSec=infinity
SendSIGKILL=no
# 安全加固
ProtectSystem=full
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
启动并设置开机自启:
1
2
3 systemctl daemon-reload
systemctl enable --now minio
systemctl status minio
确认服务处于 active (running) 状态即可。
Nginx 反向代理与 HTTPS 配置
MinIO 监听在 127.0.0.1,外部访问全部通过 Nginx 反代。这样做的好处是:统一 TLS 终止、灵活配置访问控制、方便加 CDN。这里推荐用两个子域名分别代理 API 和管理控制台:
-
1s3.yourdomain.com
— S3 API 端点(用于 SDK/CLI 访问)
-
1console.yourdomain.com
— MinIO Web 控制台
Nginx 配置:S3 API 端点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 # /etc/nginx/sites-available/s3.yourdomain.com
server {
listen 443 ssl http2;
server_name s3.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/s3.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/s3.yourdomain.com/privkey.pem;
# 允许大文件上传(MinIO 单对象最大 5TB)
client_max_body_size 0;
# 禁用请求缓冲,直接透传
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:9000;
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;
# WebSocket 支持(控制台需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
# HTTP 重定向
server {
listen 80;
server_name s3.yourdomain.com;
return 301 https://$host$request_uri;
}
Nginx 配置:Web 控制台端点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 # /etc/nginx/sites-available/console.yourdomain.com
server {
listen 443 ssl http2;
server_name console.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/console.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/console.yourdomain.com/privkey.pem;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:9001;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
别忘了在 Nginx 主配置中定义
1 | $connection_upgrade |
变量:
1
2
3
4
5 # 在 http 块中添加
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
用 Certbot 自动申请 Let’s Encrypt 证书后,启用站点并 reload Nginx:
1
2
3
4
5
6
7
8
9 # 申请证书
certbot certonly --nginx -d s3.yourdomain.com
certbot certonly --nginx -d console.yourdomain.com
# 启用站点
ln -s /etc/nginx/sites-available/s3.yourdomain.com /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/console.yourdomain.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
创建 Bucket 与基础操作
MinIO 安装完成后,通过 mc 客户端配置别名并创建第一个 Bucket:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 # 配置 mc 连接
mc alias set myminio https://s3.yourdomain.com admin 'YourStrongPassword123!@#'
# 创建 Bucket
mc mb myminio/images
mc mb myminio/backups
mc mb myminio/static
# 查看 Bucket 列表
mc ls myminio
# 上传文件
mc cp ./photo.jpg myminio/images/
mc cp --recursive ./assets/ myminio/static/
# 设置 Bucket 为公开只读(适合图床/静态资源)
mc anonymous set download myminio/images
mc anonymous set download myminio/static
# 查看访问策略
mc anonymous get myminio/images
# 输出:Access permission for 'images' is 'download'
设置为公开下载后,任何人都可以通过 URL 直接访问文件:
1 https://s3.yourdomain.com/images/photo.jpg
这对图床和静态资源托管非常方便。但请注意:绝不要把 Bucket 设为公开上传,否则会被滥用。
实际应用场景集成
场景一:WordPress 媒体库对接 MinIO
WordPress 默认将上传文件存到本地
1 | wp-content/uploads/ |
目录,对于 VPS 硬盘有限或需要 CDN 加速的场景,将媒体文件存到 MinIO 是更好的选择。
安装插件 WP Offload Media(免费版即可),配置步骤:
- Storage Provider 选择 S3 兼容存储
- Endpoint 填入
1https://s3.yourdomain.com
- Access Key 和 Secret Key 填入 MinIO 的管理员凭证(建议单独创建只读策略的子用户)
- Bucket 填入你创建的 Bucket 名称
- 开启「Remove files from server」选项,上传后自动删除本地副本
这样 WordPress 的所有图片、附件都会自动上传到 MinIO,页面渲染时从 MinIO 加载,大大减轻 VPS 本地磁盘压力。
场景二:自建图床服务
对于经常在博客、论坛中插入图片的用户,自建图床比依赖第三方服务更可控。最简单的方案是配合 MinIO 的公开 Bucket + 一个轻量前端。
这里推荐 PicGo——一款跨平台图床工具,支持 S3 协议上传:
- 在 PicGo 的图床设置中选择 Amazon S3
- 设定 Endpoint:
1https://s3.yourdomain.com
- 填入 Access Key / Secret Key
- Bucket:
1images
- 上传后自动生成 Markdown 格式链接,直接粘贴到文章中
配合
1 | mc anonymous set download |
已设置的公开访问策略,上传的图片立即可通过公网访问。
场景三:应用数据备份
MinIO 作为备份存储非常实用。下面是一个自动备份数据库并上传到 MinIO 的脚本示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 #!/bin/bash
# backup-to-minio.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/tmp/backups
MINIO_ALIAS=myminio
MINIO_BUCKET=backups
mkdir -p $BACKUP_DIR
# 备份 MySQL
mysqldump -u root -p'YourDBPassword' --all-databases \
| gzip > $BACKUP_DIR/mysql_${DATE}.sql.gz
# 备份网站文件
tar czf $BACKUP_DIR/www_${DATE}.tar.gz /var/www/html/
# 上传到 MinIO
mc cp $BACKUP_DIR/mysql_${DATE}.sql.gz ${MINIO_ALIAS}/${MINIO_BUCKET}/
mc cp $BACKUP_DIR/www_${DATE}.tar.gz ${MINIO_ALIAS}/${MINIO_BUCKET}/
# 清理本地临时文件
rm -rf $BACKUP_DIR/*
# 保留最近 30 天的备份,自动清理旧的
mc rm --recursive --force --older-than 30d ${MINIO_ALIAS}/${MINIO_BUCKET}/
echo "Backup completed: $DATE"
加入 crontab 每天凌晨 2 点自动执行:
1 0 2 * * * /usr/local/bin/backup-to-minio.sh >> /var/log/minio-backup.log 2>&1
安全加固与权限管理
对象存储暴露在公网,安全是重中之重。以下是生产环境必须做的加固措施:
1. 使用 IAM 策略替代 Root 账户日常操作
永远不要在应用中直接使用 Root 凭证。通过 mc 管理控制台创建专用的子用户和策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 # 创建只读用户策略(适合 WordPress 读取)
cat > /tmp/readwrite-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::images",
"arn:aws:s3:::images/*"
]
}
]
}
EOF
# 创建用户并附加策略
mc admin user add myminio wp-user WpUserPassword456!
mc admin policy create myminio wp-images-policy /tmp/readwrite-policy.json
mc admin policy attach myminio wp-images-policy --user wp-user
2. 防火墙只暴露必要端口
1
2
3
4
5
6
7
8
9 # 只允许 Nginx 监听的 80/443 端口
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 22/tcp
ufw enable
# 确认 MinIO 端口未暴露
curl -s http://127.0.0.1:9000/minio/health/live
# 应返回 200,但外部不应能直接访问 9000 端口
3. 启用 Bucket 版本控制
版本控制可以防止误删或覆盖重要文件:
1
2
3
4
5 # 对关键 Bucket 启用版本控制
mc version enable myminio/backups
# 查看版本状态
mc version info myminio/backups
启用后即使文件被覆盖或删除,也能通过版本 ID 恢复到任意历史版本。
性能调优与监控
MinIO 在默认配置下已经相当高效,但以下调优可以进一步提升性能:
系统层调优
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # 提高文件描述符限制
echo "minio-user soft nofile 65536" >> /etc/security/limits.conf
echo "minio-user hard nofile 65536" >> /etc/security/limits.conf
# 禁用磁盘 atime 更新(减少 IO)
# 在 /etc/fstab 中为数据分区添加 noatime 选项
# /dev/sdb1 /data ext4 defaults,noatime 0 2
# 调整内核网络参数
cat >> /etc/sysctl.conf << 'EOF'
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1
EOF
sysctl -p
MinIO 健康检查与监控
MinIO 内置了健康检查端点,可以配合外部监控使用:
1
2
3
4
5
6
7
8 # 集群健康检查
curl -s http://127.0.0.1:9000/minio/health/cluster
# 存活检查
curl -s http://127.0.0.1:9000/minio/health/live
# 就绪检查
curl -s http://127.0.0.1:9000/minio/health/ready
建议在 crontab 中加一个简单的监控脚本:
1
2
3
4
5
6
7 #!/bin/bash
# minio-health-check.sh
HEALTH=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9000/minio/health/live)
if [ "$HEALTH" != "200" ]; then
echo "MinIO health check failed! HTTP $HEALTH" | mail -s 'MinIO Alert' admin@yourdomain.com
systemctl restart minio
fi
MinIO 还内置了 Prometheus 指标端点,如果你已有 Grafana 监控体系,只需在
1 | prometheus.yml |
中添加:
1
2
3
4
5
6
7
8
9 scrape_configs:
- job_name: minio
metrics_path: /minio/v2/metrics/cluster
scheme: https
static_configs:
- targets: ['s3.yourdomain.com']
basic_auth:
username: admin
password: YourStrongPassword123!@#
常见问题与排错
上传大文件报 413 Request Entity Too Large
这是 Nginx 的
1 | client_max_body_size |
限制。前面配置中已经设为
1 | 0 |
(无限制),如果你漏掉了这一行,补上即可。
Presigned URL 签名失败或过期
MinIO 生成预签名 URL 时依赖服务器时间。确保 VPS 时区正确并开启 NTP 同步:
1
2 timedatectl set-ntp true
timedatectl status
mc 命令报 S3 API 错误
检查
1 | MINIO_SERVER_URL |
是否与实际访问域名一致。如果 Nginx 反代后域名变化,MinIO 内部生成的签名 URL 会指向错误地址。
Bucket 设为公开后仍返回 403
确认 Nginx 正确转发了
1 | X-Forwarded-Proto |
头。MinIO 在 HTTPS 反代场景下需要知道原始协议是 HTTPS,否则签名校验会失败。
成本对比:自建 vs 云厂商
以一个典型的个人/小团队场景为例:存储 100GB 数据,每月公网下载 500GB:
| 方案 | 月存储费 | 月流量费 | 月总费用 |
|---|---|---|---|
| AWS S3 | $2.30 | $45.00 | $47.30 |
| 阿里云 OSS | ¥12.00 | ¥250.00 | ¥262.00 |
| VPS + MinIO(5美元/月) | 包含 | 包含 | $5.00 |
可以看到,当你有一定量的公网下载需求时,VPS 自建方案的成本优势非常明显——云厂商的流量费远高于存储费,而 VPS 的带宽是包月的。当然前提是你选的 VPS 带宽足够(建议至少 100Mbps 或 1TB/月流量包)。
总结
在 VPS 上用 MinIO 自建对象存储,是对公有云 S3 的高性价比替代方案,特别适合以下场景:
- 图床和静态资源托管,需要大量公网读取但不想付流量费
- WordPress 等应用的媒体文件远程存储
- 应用数据和数据库的定期备份
- 团队内部文件共享和协作
核心步骤回顾:安装 MinIO → 配置 systemd 服务 → Nginx 反代 + HTTPS → 创建 Bucket 和 IAM 策略 → 对接你的应用。整个过程不超过 30 分钟,就能拥有一个完全受控的私有 S3 存储。数据在你自己的硬盘上,不担心供应商锁定,不担心突然涨价,也不担心别人哪天关停服务。
如果你的项目正在成长,数据量逐步增加,从自建 MinIO 起步、未来再按需迁移到公有云,这是一条平滑且经济的演进路线。
汤不热吧