MinIO is an object storage server that implements the Amazon S3 API, so applications, backup tools and SDKs written for S3 can store data on your own server instead. In this tutorial you will install MinIO on Ubuntu 24.04 in single-node mode, run it as a systemd service bound to localhost, publish the S3 API and web console through Nginx with Let's Encrypt certificates, and manage buckets, users and policies with the mc command-line client.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges. 2 GB of RAM is enough for a small single-node deployment. - Storage for the objects. A separate disk or volume formatted with XFS and mounted at
/srv/miniois recommended; a directory on the root disk works for testing. - Two DNS A records pointing to the server:
s3.your_domainfor the S3 API andconsole.your_domainfor the web console. - Nginx installed (
sudo apt install nginx) and UFW allowing SSH and web traffic:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Notesince late 2025 the open-source MinIO Community Edition is in maintenance mode. The upstream project no longer publishes new pre-built community releases or Docker images, and the web console in recent community builds only includes the object browser; users, policies and settings are managed with
mc. The steps below use the community binary. For long-term new deployments, also evaluate actively developed S3-compatible alternatives such as Garage or SeaweedFS.
Step 1 - Installing the MinIO server binary
MinIO is a single static binary. Download it to /usr/local/bin and make it executable:
sudo curl -fL -o /usr/local/bin/minio https://dl.min.io/server/minio/release/linux-amd64/minio
sudo chmod +x /usr/local/bin/minio
On an ARM64 server, replace linux-amd64 with linux-arm64. Check the version:
minio --version
minio version RELEASE.2025-10-15T17-29-55Z (commit-id=...)
Runtime: go1.24.6 linux/amd64
If the download is no longer available, build the binary from source with a current Go toolchain (go install github.com/minio/minio@latest) and copy it to /usr/local/bin/minio.
Step 2 - Creating the service user and data directory
Run MinIO as an unprivileged system user that owns the data directory:
sudo useradd --system --user-group --no-create-home --shell /usr/sbin/nologin minio-user
sudo mkdir -p /srv/minio/data
sudo chown -R minio-user:minio-user /srv/minio
If /srv/minio is a dedicated disk, make sure it is mounted (and listed in /etc/fstab) before running chown, so the ownership applies to the mounted filesystem.
Step 3 - Configuring MinIO
The systemd unit reads its settings from /etc/default/minio. Create the file:
sudo nano /etc/default/minio
MINIO_VOLUMES="/srv/minio/data"
MINIO_OPTS="--address 127.0.0.1:9000 --console-address 127.0.0.1:9001"
MINIO_ROOT_USER="your_admin_user"
MINIO_ROOT_PASSWORD="your_strong_password"
MINIO_SERVER_URL="https://s3.your_domain"
MINIO_BROWSER_REDIRECT_URL="https://console.your_domain"
MINIO_VOLUMESis the data path. A single path runs MinIO in single-node, single-drive mode.MINIO_OPTSbinds the S3 API (port 9000) and console (port 9001) to localhost only; Nginx will be the public entry point.MINIO_ROOT_USERandMINIO_ROOT_PASSWORDare the administrator credentials. Replace the placeholders; the password must be at least 8 characters, and a long random value (openssl rand -base64 24) is better.MINIO_SERVER_URLandMINIO_BROWSER_REDIRECT_URLtell MinIO its public URLs, which it uses for presigned URLs and console redirects.
The file contains the root password, so restrict it:
sudo chmod 640 /etc/default/minio
sudo chown root:minio-user /etc/default/minio
Step 4 - Running MinIO as a systemd service
Create the unit file, based on the one MinIO publishes:
sudo nano /etc/systemd/system/minio.service
[Unit]
Description=MinIO object storage
Documentation=https://github.com/minio/minio
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio
[Service]
User=minio-user
Group=minio-user
WorkingDirectory=/usr/local
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=65536
TasksMax=infinity
TimeoutStopSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
Start the service and enable it at boot:
sudo systemctl daemon-reload
sudo systemctl enable --now minio
sudo systemctl status minio --no-pager
● minio.service - MinIO object storage
Loaded: loaded (/etc/systemd/system/minio.service; enabled; preset: enabled)
Active: active (running) since Fri 2026-09-25 10:04:12 UTC; 5s ago
Check the liveness endpoint locally:
curl -I http://127.0.0.1:9000/minio/health/live
HTTP/1.1 200 OK
If the service fails, sudo journalctl -u minio -n 50 shows the reason, usually a permissions problem on the data directory or a root password that is too short.
Step 5 - Publishing the API and console through Nginx
Nginx terminates TLS and forwards requests to MinIO. The S3 API needs unlimited body size and no buffering so large uploads stream straight through, and the console needs WebSocket support. Create the site file:
sudo nano /etc/nginx/sites-available/minio
server {
listen 80;
listen [::]:80;
server_name s3.your_domain;
client_max_body_size 0;
proxy_buffering off;
proxy_request_buffering off;
ignore_invalid_headers 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;
}
}
server {
listen 80;
listen [::]:80;
server_name console.your_domain;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:9001;
}
}
Host $http_host matters: S3 request signatures include the host name, so MinIO must see the same host the client signed.
Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/minio /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Now obtain certificates for both names. The Certbot Nginx plugin adds the listen 443 ssl lines and an HTTP-to-HTTPS redirect to each server block:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d s3.your_domain -d console.your_domain
Check the API over HTTPS:
curl -I https://s3.your_domain/minio/health/live
HTTP/2 200
Open https://console.your_domain in a browser and sign in with the root user and password from Step 3. You should see the object browser with no buckets yet.
Step 6 - Installing and configuring the mc client
mc is MinIO's command-line client. Install it as mc, but be aware that Ubuntu's Midnight Commander package also installs a command called mc; if you use it, install the MinIO client under another name such as mcli and adjust the commands below.
sudo curl -fL -o /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc
sudo chmod +x /usr/local/bin/mc
Create an alias called myminio that stores the endpoint and root credentials in ~/.mc/config.json. Running mc alias set without the keys makes it prompt for them, which keeps the password out of your shell history:
mc alias set myminio https://s3.your_domain
Confirm the connection and server status:
mc admin info myminio
● s3.your_domain
Uptime: 12 minutes
Version: 2025-10-15T17:29:55Z
Network: 1/1 OK
Drives: 1/1 OK
Pool: 1
Step 7 - Creating a bucket and uploading objects
Create a bucket for backups, upload a file and list it:
mc mb myminio/backups
echo "hello from minio" > /tmp/hello.txt
mc cp /tmp/hello.txt myminio/backups/
mc ls myminio/backups
Bucket created successfully `myminio/backups`.
[2026-09-25 10:15:02 UTC] 17B STANDARD hello.txt
Enable versioning so overwritten or deleted objects can be recovered, and add a lifecycle rule that removes old versions after 30 days so they do not grow forever:
mc version enable myminio/backups
mc ilm rule add --noncurrent-expire-days 30 myminio/backups
mc ilm rule ls myminio/backups
mc mirror is useful for copying whole directories, for example mc mirror /var/backups myminio/backups/server01.
Step 8 - Creating a restricted user for an application
Applications should never use the root credentials. Create a policy that only allows access to the backups bucket:
nano ~/backups-rw.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::backups"]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::backups/*"]
}
]
}
Load the policy, create a user and attach the policy to it. Replace app_secret_key with a long random value of at least 8 characters:
mc admin policy create myminio backups-rw ~/backups-rw.json
mc admin user add myminio backup-app app_secret_key
mc admin policy attach myminio backups-rw --user backup-app
Test the restriction with a second alias using the new credentials:
mc alias set backupapp https://s3.your_domain backup-app app_secret_key
mc ls backupapp/backups
mc mb backupapp/other-bucket
The listing works, while creating another bucket is refused:
[2026-09-25 10:15:02 UTC] 17B STANDARD hello.txt
mc: <ERROR> Unable to make bucket `backupapp/other-bucket`. Access Denied.
Any S3 client can now use these credentials with the endpoint https://s3.your_domain. Most tools also need a region; MinIO accepts us-east-1 by default. Enable path-style addressing in the client if it offers the option, because this setup does not serve bucket names as subdomains.
Troubleshooting
Uploads fail with "413 Request Entity Too Large". The Nginx server block for the API is missing client_max_body_size 0;, or Certbot created a separate block without it. Check /etc/nginx/sites-available/minio and reload Nginx.
Clients get "SignatureDoesNotMatch". The proxy changed the Host header. Make sure the API block uses proxy_set_header Host $http_host; and that clients connect to the exact name in MINIO_SERVER_URL.
The console redirects to 127.0.0.1:9001. MINIO_BROWSER_REDIRECT_URL is missing or wrong in /etc/default/minio. Fix it and run sudo systemctl restart minio.
MinIO refuses to start after moving the data disk. The mount point is owned by root. Run sudo chown -R minio-user:minio-user /srv/minio with the disk mounted.
Conclusion
You now have an S3-compatible MinIO server on Ubuntu 24.04, reachable over HTTPS through Nginx, with a versioned bucket and a least-privilege user for applications. From here you can point backup tools such as restic or rclone at https://s3.your_domain, schedule mc mirror jobs, or plan a multi-node deployment with erasure coding when a single server is no longer enough.
