SSD Nodes Learn 8GB 記憶體 — 每年 $66
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-01

如何在 Ubuntu 24.04 VPS 自架 MinIO

在單一 Ubuntu 24.04 VPS 建立自有 S3 API,涵蓋驗證二進位檔、systemd、mc、預簽署 URL 與 restic 備份;社群版二進位檔版本已固定,2025 年 9 月後的安全修正不包含其中。

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

MinIO 提供的自架物件儲存

MinIO 是支援 Amazon S3 API 的自架物件儲存。將 restic 或任何 S3 SDK 指向您自己的伺服器,只需變更一項端點設定,客戶端就無法察覺差異。本指南會在 Ubuntu 24.04 上建立單一節點:驗證過的二進位檔、專用系統使用者、將 root 憑證排除在 unit 檔案之外的 systemd 單元,以及供 restic 備份資料的 bucket。

S3(簡易儲存服務)是 HTTP API,而不是檔案系統。您可以使用 PUT 將物件寫入 bucket 的 key,並使用 GET 取回物件;它不支援部分寫入,也不支援重新命名。備份工具適合這種模型,因為物件不是完整抵達,就是未抵達。

單一節點只保留資料的一份副本。這就是您所做的取捨。您只需支付 VPS 的費用,就能取得自行控制的 S3 端點;但也必須自行處理雲端服務供應商原本負責的所有工作,包括更換故障磁碟和修補伺服器軟體。本指南接近結尾的章節會明確說明這項取捨何時合理。

2026 年 7 月 MinIO 社群版的狀態

建置前請先閱讀本節,因為近期已有變更。2025 年 5 月,MinIO 移除了社群版 Web 主控台中的管理功能。瀏覽器中僅保留物件瀏覽器,因此現在必須改用 mc 命令列用戶端管理 bucket 和存取金鑰。

2025 年稍後,MinIO 停止發布預先編譯的社群版二進位檔。專案 README 現在表示,社群版僅以原始碼形式發行。較舊的下載 URL 仍可使用:截至 2026 年 7 月,這些 URL 會提供伺服器 build RELEASE.2025-09-07T16-13-09Z 和用戶端 build RELEASE.2025-08-13T08-35-41Z,且尚未出現更新的社群版 build。因此,下方的二進位檔確實存在且可執行,但版本已固定。2025 年 9 月之後發布的安全性修正不包含在其中。

這項事實決定了本指南其餘部分的設計。因此,這裡的 MinIO 會在 127.0.0.1 上接聽,且只能透過你控制的 proxy 連至網際網路。如果你希望持續取得修正,請從原始碼建置。廠商 README 提供單一命令 go install github.com/minio/minio@latest;該命令需要 Go 工具鏈,並會將二進位檔寫入 ~/go/bin/minio。將該二進位檔安裝至 /usr/local/bin/minio 後,這裡的其他步驟均不需變更。

安裝 MinIO 二進位檔並驗證下載內容

下載指定版本的發行版本及其公布的 checksum。-f 旗標會讓 curl 在發生 HTTP 錯誤時失敗,而不是將錯誤頁面以你指定的名稱儲存。否則可能會安裝 404 頁面,並疑惑它為何無法執行。

cd /tmp
REL=RELEASE.2025-09-07T16-13-09Z
curl -fsSL "https://dl.min.io/server/minio/release/linux-amd64/archive/minio.$REL" -o minio
curl -fsSL "https://dl.min.io/server/minio/release/linux-amd64/archive/minio.$REL.sha256sum" -o minio.sha256sum

比對這兩個雜湊值,且只比對雜湊值。

published=$(awk '{print $1}' minio.sha256sum)
downloaded=$(sha256sum minio | awk '{print $1}')
[ "$published" = "$downloaded" ] && echo "checksum ok"

這裡不要使用 sha256sum -c minio.sha256sum。該檔案中寫在雜湊值後面的標籤是 minio.RELEASE.2025-09-07T16-13-09Z,而我們將下載內容儲存為 minio,因此 -c 會尋找不存在的檔案。它會回報 No such file or directory,接著回報 WARNING: 1 listed file could not be read。這看起來像是下載內容損毀,但實際上並不是。標籤只是一個名稱。雜湊值才是提供完整性保證的部分。

請明確了解這項檢查能證明什麼。二進位檔與雜湊值都由同一個供應商,透過同一個連線提供,因此比對相符可證明下載內容完整,且在傳輸途中未損壞或遭到修改。但這無法證明供應商值得信任。那是另一個問題,沒有任何 sha256sum command 可以解決。

sudo install -o root -g root -m 755 minio /usr/local/bin/minio
minio --version

minio --version 會先輸出 minio version RELEASE.2025-09-07T16-13-09Z,接著輸出幾行 build 資訊。這裡出現 Permission denied 表示模式不正確,而 command not found 表示 /usr/local/bin 不在你的 PATH 中。

建立系統使用者與資料目錄

MinIO 接受來自網路的上傳,因此不應以 root 身分執行。為它建立沒有家目錄且沒有登入 shell 的帳戶。

sudo groupadd -r minio-user
sudo useradd -M -r -g minio-user -s /usr/sbin/nologin minio-user
sudo mkdir -p /var/lib/minio/data
sudo chown -R minio-user:minio-user /var/lib/minio
sudo chmod 750 /var/lib/minio

-r 會建立 UID 低於 1000 的系統帳戶,使其不在一般使用者所使用的範圍內。-M 會略過家目錄,因為永遠不登入的帳戶不需要保留家目錄。使用 id minio-user 檢查結果,並使用 stat -c '%U %a' /var/lib/minio 檢查;該命令應輸出 minio-user 750

資料目錄必須可由該使用者寫入,而不只是可讀取。MinIO 第一次啟動時,會在磁碟區內建立 .minio.sys 目錄,用於儲存自身的設定,因此由 root 擁有的目錄會使 MinIO 在啟動期間結束執行,並顯示以 permission denied 結尾的訊息。以這種方式執行的每項服務都適用相同規則,VPS 上的最低權限服務使用者 會完整說明相關做法。

將 root 憑證放入環境檔案

root 憑證可以存取每個 bucket,因此不應放在所有使用者都可讀取的 unit file 中。請先以正確的模式建立檔案,再寫入內容。如此一來,密碼不會有任何時刻存在於可讀取的檔案中。

sudo install -o root -g root -m 600 /dev/null /etc/default/minio
printf 'MINIO_ROOT_USER=minio-root\nMINIO_ROOT_PASSWORD=%s\nMINIO_VOLUMES="/var/lib/minio/data"\nMINIO_OPTS="--address 127.0.0.1:9000 --console-address 127.0.0.1:9001"\n' "$(openssl rand -base64 24)" | sudo tee /etc/default/minio > /dev/null
sudo sed -n 's/^MINIO_ROOT_PASSWORD=//p' /etc/default/minio

tee 會截斷現有檔案,而不是重新建立檔案,因此模式會維持為 600,擁有者也會維持為 root。這是刻意的設計。systemd 會在降低權限至 User= 之前,以 root 身分讀取 EnvironmentFile,因此服務帳戶不需要讀取自己的憑證。服務啟動後,使用 sudo -u minio-user cat /etc/default/minio 驗證。該命令必須輸出 Permission denied

啟動前,請先了解 MinIO 的兩項行為。若其環境中沒有 MINIO_ROOT_USERMINIO_ROOT_PASSWORD,MinIO 不會拒絕啟動。它會使用文件所述的預設憑證 minioadmin:minioadmin 啟動。這是任何掃描器都會先嘗試的第一組憑證,而且 MinIO 在使用這組憑證時看起來完全正常。密碼若少於 8 個字元則會被拒絕。MinIO 會在啟動時結束,並顯示憑證無效的錯誤,因為 access key 至少需要 3 個字元,而 secret key 至少需要 8 個字元。

MINIO_VOLUMES 是資料路徑,MINIO_OPTS 儲存 flags。繫結至 127.0.0.1 表示 VPS 外部目前無法連線至 S3 API,這是適當的預設設定。之後再透過持有憑證的 proxy,依需求開放存取。

撰寫 systemd 單元

建立 /etc/systemd/system/minio.service

[Unit]
Description=MinIO object storage
Documentation=https://github.com/minio/minio
Wants=network-online.target
After=network-online.target

[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_VOLUMES $MINIO_OPTS
Restart=always
RestartSec=5
LimitNOFILE=65536
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

EnvironmentFile 開頭沒有 -,這是刻意的決定,不是筆誤。加入連字號後,systemd 會忽略遺失的檔案,並繼續啟動 MinIO。因此,檔案遭刪除或路徑拼寫錯誤時,系統會無提示地讓伺服器以 minioadmin:minioadmin 執行。不加連字號時,遺失的檔案會在 MinIO 執行前使單元失敗,且 journalctl -u minio 會顯示 Failed to load environment files: No such file or directory。拒絕啟動的單元,比悄悄接受預設密碼的伺服器更容易察覺。

$MINIO_VOLUMES$MINIO_OPTS 刻意不加引號,因為 systemd 會將未加引號的變數依空白拆分成個別引數。這就是 MINIO_OPTS 中的 4 個字詞會成為 minio server 的 4 個引數的原因。LimitNOFILE=65536 會提高檔案描述元上限,因為每個開啟的連線和資料檔案都會占用 1 個描述元,而預設值 1024 在負載增加時很快就會耗盡。

sudo systemctl daemon-reload
sudo systemctl enable --now minio
systemctl is-active minio
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9000/minio/health/live

is-active 應輸出 active,而健康狀態端點應回應 200journalctl -u minio -n 20 --no-pager 會顯示伺服器正在監聽的 API 位址。如果單元持續重新啟動,systemd 會放棄並記錄 Start request repeated too quickly。這表示 MinIO 每次嘗試都會結束執行;原因會顯示在該訊息上方的幾行,因此請向上查看。

若要進一步隔離,請將 ProtectSystem=fullProtectHome=true 加入 [Service] 區段。兩者都需要主機核心提供 mount namespace。在共用主機核心的容器虛擬化環境中,例如 OpenVZ 或 LXC,這些設定可能會失敗,單元隨後會回報 status=226/NAMESPACE。移除這兩行後即可啟動。此單元本身是一般單元,而 VPS 上的 systemd 服務與計時器涵蓋其餘指示詞。

安裝 mc 並證明往返運作

MinIO 用戶端是 mc。請勿使用 apt install mc 安裝。該套件是 Midnight Commander,是與 MinIO 無關的檔案管理員。

cd /tmp
curl -fsSL https://dl.min.io/client/mc/release/linux-amd64/mc -o mc
curl -fsSL https://dl.min.io/client/mc/release/linux-amd64/mc.sha256sum -o mc.sha256sum
[ "$(awk '{print $1}' mc.sha256sum)" = "$(sha256sum mc | awk '{print $1}')" ] && echo "checksum ok"
sudo install -o root -g root -m 755 mc /usr/local/bin/mc

將伺服器註冊為別名,然後透過該伺服器移動物件。

MINIO_PASS=$(sudo sed -n 's/^MINIO_ROOT_PASSWORD=//p' /etc/default/minio)
mc alias set local http://127.0.0.1:9000 minio-root "$MINIO_PASS"
mc mb local/backups
echo "hello object storage" > /tmp/hello.txt
mc cp /tmp/hello.txt local/backups/hello.txt
mc ls local/backups
mc cat local/backups/hello.txt

mc ls 應列出 hello.txt 及其大小,而 mc cat 應輸出 hello object storage。這次往返操作才是伺服器正常運作的實際證明,因為它會建立其他用戶端也會建立的相同已簽署 S3 請求。如果需要另一項確認,mc admin info local 會輸出伺服器狀態。

現在再執行一項檢查,此時伺服器仍是空的。

mc alias set defaultcheck http://127.0.0.1:9000 minioadmin minioadmin

此命令必須失敗。如果成功,表示環境檔案從未傳遞給處理程序,伺服器目前使用的是預設認證。請先修正此問題,再讓任何其他項目存取這台機器。

mc 會以純文字將別名儲存在 ~/.mc/config.json,因此這些認證會位於執行該命令之使用者的家目錄中。在 sudo 下執行 mc 會將 root 認證寫入 /root/.mc/config.json。請只在一個管理員帳戶中保留 root 別名,並為每個應用程式提供個別金鑰。

使用預簽章 URL 提供單一物件

預簽章 URL 是附帶簽章和到期時間的普通 HTTPS 連結。任何持有該連結的人,都可以不需要帳戶或用戶端,擷取該單一物件。

mc share download --expire 12h local/backups/hello.txt

輸出內容會在查詢字串中包含 X-Amz-SignatureX-Amz-Expires。其中有兩點常讓人感到意外。此連結會使用您所用別名中的端點建立,因此在 127.0.0.1 上建立的別名會產生只有這台機器能開啟的連結。若要分享連結,請在公開主機名稱上建立第二個別名。另一點是,系統沒有撤銷按鈕。簽章會持續有效直到到期,因此您唯一能使用的控制方式就是設定較短的有效期限。S3 簽章格式允許的最長期限是 7 天。

為 restic 建立專用金鑰和 bucket

root 憑證可以讀取並刪除所有 bucket,因此備份工作不應持有這些憑證。建立一個 bucket、一個限定於該 bucket 的原則,以及一個不具備其他權限的使用者。

mc mb local/restic
cat > /tmp/restic-rw.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
      "Resource": ["arn:aws:s3:::restic"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::restic/*"]
    }
  ]
}
EOF
RESTIC_KEY=$(openssl rand -base64 24)
mc admin policy create local restic-rw /tmp/restic-rw.json
mc admin user add local restic-backup "$RESTIC_KEY"
mc admin policy attach local restic-rw --user restic-backup

MinIO 提供內建的 readwrite policy。使用它可以少執行一個命令,但它會授予對伺服器上所有 bucket 的完整存取權。上方的 policy 刻意將 bucket 名稱寫入兩次:一次使用 arn:aws:s3:::restic,讓 bucket 列舉能夠運作;另一次使用 arn:aws:s3:::restic/*,指定其中的物件。在 S3 中,bucket 與其中的物件是不同的資源,因此只指定其中一者會失敗,而且錯誤表現看起來像是用戶端損壞。

在信任這項限制前先進行測試。

mc alias set resticuser http://127.0.0.1:9000 restic-backup "$RESTIC_KEY"
mc ls resticuser/restic
mc ls resticuser/backups

第一個 ls 會成功,第二個會因 Access Denied 而失敗。未經測試的 policy 只是猜測。

現在讓 restic 使用這個 bucket。restic 會從標準 AWS 環境變數讀取 S3 憑證,因此不需要 restic 專用的憑證檔案。

sudo apt install -y restic
export AWS_ACCESS_KEY_ID=restic-backup
export AWS_SECRET_ACCESS_KEY="$RESTIC_KEY"
restic -r s3:http://127.0.0.1:9000/restic init
restic -r s3:http://127.0.0.1:9000/restic backup /etc
restic -r s3:http://127.0.0.1:9000/restic snapshots

restic init 會要求輸入 repository 密碼。該密碼會加密 repository,因此 MinIO 只會儲存密文;遺失密碼也會遺失備份。由 systemd timer 啟動的執行工作沒有終端機可供輸入,因此請將 RESTIC_PASSWORD_FILE 設為 mode 600 的檔案,以用於排程備份。

有一項儲存位置規則比上述任何命令都重要。將 restic repository 放在與受保護資料相同的 VPS 上,只能讓您避免不良的 rm,無法避免其他問題。MinIO 節點應位於不同的機器上,最好位於不同的 region。VPS 上的 restic 備份說明如何在此基礎上設定排程和保留策略。

使用 nginx 終止 TLS

MinIO 位於 localhost,因此 nginx 是對外服務介面。請先依照 使用 certbot 和 nginx 取得 Let's Encrypt 憑證 簽發憑證,然後使用以下伺服器區塊。

server {
    listen 443 ssl;
    server_name s3.example.com;

    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;
    proxy_request_buffering off;

    location / {
        proxy_set_header Host $http_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_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;
        proxy_pass http://127.0.0.1:9000;
    }
}

其中幾行設定會直接影響功能。client_max_body_size 0 移除預設的 1 MB 請求本文大小限制,否則任何較大的上傳都會在 MinIO 收到請求前,先因 413 Request Entity Too Large 而遭拒絕。proxy_request_buffering off 會直接串流上傳內容,因為預設設定會先將整個請求寫入暫存檔,而大型物件會因此需要兩倍的磁碟空間。proxy_set_header Host $http_host 是較容易忽略的設定:S3 簽章會涵蓋 Host 標頭,因此 Proxy 若改寫該標頭,所有請求都會因 SignatureDoesNotMatch 而失敗,即使存取日誌顯示請求已正常送達。

另外,請告知 MinIO 其對外公開名稱,讓它產生的連結指向 Proxy,而不是 localhost。

echo 'MINIO_SERVER_URL=https://s3.example.com' | sudo tee -a /etc/default/minio
sudo systemctl restart minio

防火牆設定保持精簡。允許 SSH 和 HTTPS,完全不要為連接埠 9000 和 9001 設定規則,因為繫結至 127.0.0.1 的位址無論防火牆設定為何,都無法從其他機器連線。VPS 上的 ufw 防火牆基本設定 說明所需的命令。

何時單節點 MinIO 已足夠,以及何時應選擇真正的 S3

此處的單節點是指使用 1 個磁碟且沒有任何 parity。MinIO 的官方文件將此配置描述為適合測試,以及不要求可用性的小型工作負載。此部署內沒有第 2 份副本,因此每個物件的耐久性取決於單一 VPS 磁碟的耐久性。需要分散式 erasure-coded 後端的功能,包括 bucket replication 和 object locking,屬於多磁碟部署的功能。因此,請勿在此配置上向任何人承諾不可變的保留原則。

它適合作為另一個區域中第 2 台 VPS 的 restic 目標,也適合作為開發工作和 CI 成品的 S3 endpoint;在這些情況下,遺失 bucket 的代價只是重新建置,沒有其他損失。它也適合小型應用程式中的使用者上傳,但前提是您負責復原計畫,且確實測試過還原流程。

如果合約或監管機構要求 object lock 或多區域耐久性,或者您不想在 03:00 因磁碟已滿而收到待處理通知,請選擇代管式 S3。凍結的 build 是另一個合理原因。截至 July 2026,預先編譯的 community binary 仍是 September 2025 的版本,且不再收到修正,因此執行它代表您接受這項限制;另一個選擇是從原始碼建置,並自行持續追蹤專案更新。

有一項界線值得說明,因為這個問題經常出現。物件儲存不是資料庫。每次寫入都會取代整個物件,因此將正在使用的 SQL 檔案放在 S3 bucket 上既慢又不安全。請將資料庫保留在本機磁碟,並改為備份至 bucket:在 VPS 上於正式環境執行 SQLite 說明了這種配置。

失敗模式與您會看到的訊息

systemctl enable --now 後單元立即失敗。 讀取 journalctl -u minio -n 30 --no-pagerFailed to load environment files: No such file or directory 表示單元中缺少 /etc/default/minio,或其路徑拼寫錯誤。以 permission denied 結尾的訊息表示服務帳戶無法寫入資料目錄,因此請確認 stat -c '%U' /var/lib/minio/data 顯示 minio-user

minioadmin:minioadmin 仍可登入。 環境檔案從未傳遞至程序。確認單元包含 EnvironmentFile=/etc/default/minio,執行 sudo systemctl daemon-reload,然後重新啟動服務。MinIO 只會在啟動時讀取其 root 憑證,因此未重新啟動就編輯該檔案不會產生任何變更。

啟動時出現 Address already in use 其他程序已占用 port 9000。變更 MinIO 的 port 前,使用 sudo ss -ltnp | grep :9000 找出該程序。

透過 proxy 上傳超過 1 MB 的檔案失敗。 nginx 回應了 413 Request Entity Too Large,MinIO 從未收到該請求。請在 server block 中設定 client_max_body_size 0

SignatureDoesNotMatch secret key 不正確,或用戶端與 MinIO 之間的某個元件改寫了 Host 標頭;該標頭包含在簽章中。

RequestTimeTooSkewed 用戶端或伺服器的時鐘不正確。每個 S3 請求都會帶有時間戳記,超出 15 分鐘的時間範圍就會遭到拒絕。檢查 timedatectl,並確認時間同步功能已啟用。

在您確定存在的 bucket 上出現 Access Denied 該 key 的權限範圍指向不同的 bucket。使用 mc admin policy info local restic-rw 顯示原則實際允許的內容,並比對 resource 行中的 bucket 名稱。

FAQ

單節點 MinIO 足以用於實際備份嗎?

如果將其作為 restic 的目標,且執行在與受保護資料不同的機器上,則足夠使用。但不能將其作為唯一副本。單磁碟部署沒有任何 parity,因此 MinIO 內沒有第二份副本。如果該 VPS 磁碟遺失資料,物件也會遺失。請在其他位置保留第二個目標,並至少從兩個目標各還原一次,以確認程序可正常運作。

為什麼 sha256sum -c 會在 MinIO 的 checksum 檔案上失敗?

因為該檔案中雜湊值後面的標籤是發行版本名稱 minio.RELEASE.2025-09-07T16-13-09Z,而您下載的檔案通常名為 miniosha256sum -c 會尋找 checksum 檔案內所寫名稱的檔案,但找不到該檔案,因此回報 No such file or directoryWARNING: 1 listed file could not be read。下載內容沒有問題。請直接比較雜湊字串並忽略該標籤,因為它不具備安全性意義。

MinIO 管理 Web 主控台去哪裡了?

MinIO 在 May 2025 從 community edition 主控台移除管理功能,只在 Web 介面中保留物件瀏覽器。現在必須使用 mc client,並透過 mc admin user addmc admin policy attach 等命令管理 buckets 與 users。這是 community edition 支援的操作方式,而不是替代方案,因此本指南全部使用命令列操作。

如何將 restic 指向 MinIO 作為 S3 後端?

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY 設定為 MinIO access key 及其 secret,然後使用格式為 s3:https://s3.example.com/restic 的 repository 字串,其中最後一個路徑元素是 bucket 名稱。請先使用 mc mb 建立 bucket,因為限定於單一 bucket 的 key 沒有建立 bucket 的權限。restic 會在上傳前使用自身的 repository password 加密所有內容,因此 MinIO 儲存的是密文,且不會看到您的檔案。

必須讓 MinIO 在 nginx 後方執行嗎?

只要 client 不在同一台機器上,就需要 TLS (transport layer security),因為 S3 credentials 和物件資料都會在 request 內傳輸。使用 port 443 上由 certbot 提供憑證的 proxy,是最簡單的作法,也能讓憑證更新作業與 MinIO 分離。如果將 --certs-dir 指向存放 public.crtprivate.key 的目錄,MinIO 也能自行終止 TLS;但此時 service account 必須具備讀取更新後 private key 的權限,為了相同結果需要額外處理。

#minio#s3#object-storage#self-hosted#vps