Caption: MinIO provides an S3-compatible object storage service that you can run on your own VPS or private server.
Introduction
MinIO is an open-source object storage server with an API compatible with Amazon S3. It is commonly used for application uploads, backups, media files, data lake experiments, CI artifacts, and internal developer platforms that need object storage without sending every file to a public cloud provider. If an application can talk to S3, there is a good chance it can talk to MinIO by changing the endpoint, access key, secret key, and bucket name.
Self-hosting object storage is different from running a blog, wiki, or dashboard. The service often becomes a dependency for other applications, and those applications may assume that uploaded files are durable, private, and available during restores. A careless MinIO deployment can expose buckets publicly, lose data during disk failure, or leave root credentials inside application configuration. A careful deployment gives you a compact storage layer with predictable costs, simple S3 tooling, and full control over where data lives.
This guide installs MinIO on Ubuntu 24.04 LTS using Docker Compose. The container listens only on localhost, Nginx terminates HTTPS for the S3 API and the web console, and the MinIO Client (mc) handles bucket creation, versioning, user management, and operational checks. By the end, you will have a private MinIO service at https://s3.example.com, a console at https://console.s3.example.com, a test bucket, a scoped application user, and a backup routine you can rehearse before trusting the service with important data.
Why Choose MinIO?
- S3-compatible API: Many backup tools, CMS platforms, Laravel applications, media libraries, and data tools already support S3-style endpoints.
- Small operational footprint: A single Docker container can serve a personal or small-team deployment when paired with reliable disks and backups.
- Fast local storage: Keeping object data near your applications can reduce latency and bandwidth costs for internal services.
- Portable tooling: The
mcclient can create buckets, mirror data, inspect objects, manage users, and test the deployment from a terminal. - Private infrastructure: You decide the server location, firewall policy, backup target, access model, and retention plan.
- Clear growth path: The same concepts apply when you later move to dedicated storage nodes, distributed MinIO, or a managed S3 service.
MinIO is a strong fit for self-hosters who want an S3 endpoint for backups, internal apps, staging environments, or small production workloads. It is not a replacement for a complete disaster-recovery plan. If the data matters, store another copy outside the MinIO host and regularly test restore procedures.
Prerequisites
Hardware Recommendations:
- 2 CPU cores and 2 GB RAM for a small personal or team deployment
- SSD or NVMe storage for better metadata and multipart upload performance
- Enough disk space for active objects, object versions, failed multipart uploads, logs, and local backup archives
- A second storage location for off-server backups, such as another VPS, NAS, or S3-compatible provider
Software and Accounts:
- Ubuntu 24.04 LTS server with sudo access
- A domain such as
s3.example.comfor the S3 API - A second domain such as
console.s3.example.comfor the MinIO Console - DNS
AorAAAArecords for both domains pointing to the server - Docker Engine and the Docker Compose plugin
- Nginx and Certbot for HTTPS
- A password manager or secrets manager for root and application credentials
Security Notes:
- Do not use MinIO's default
minioadmincredentials on any internet-reachable host. - Keep the container ports bound to
127.0.0.1; expose only HTTPS through Nginx. - Use the MinIO root account for administration only, not for applications.
- Create scoped users and policies for each app or backup job.
- Back up object data and configuration, then test restores before storing critical files.
Start with an updated host and a small firewall policy:
sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg git ufw nginx certbot python3-certbot-nginx
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
Installation Guide
This deployment uses Docker Compose, a local bind-mounted data directory, and two Nginx virtual hosts. The S3 API runs behind s3.example.com; the browser console runs behind console.s3.example.com.
1. Install Docker Engine
Install Docker and confirm that the Compose plugin is available:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker
docker --version
docker compose version
If your shell does not pick up the new docker group immediately, log out and back in before continuing.
2. Create the MinIO Project Directory
Keep the Compose file, environment file, data directory, and backup workspace together under /opt/minio:
sudo mkdir -p /opt/minio/data /opt/minio/backups
sudo chown -R "$USER":"$USER" /opt/minio
chmod 700 /opt/minio/data /opt/minio/backups
cd /opt/minio
The data directory stores object data and MinIO metadata. The backups directory is only a local staging area; important backups should be copied to another machine or storage provider.
3. Generate Root Credentials and Environment Settings
Create strong root credentials and write them to an environment file:
cd /opt/minio
MINIO_ROOT_USER_VALUE="minio-root-$(openssl rand -hex 6)"
MINIO_ROOT_PASSWORD_VALUE="$(openssl rand -base64 48 | tr -d '\n')"
cat > .env <<EOF
MINIO_ROOT_USER=${MINIO_ROOT_USER_VALUE}
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD_VALUE}
MINIO_SERVER_URL=https://s3.example.com
MINIO_BROWSER_REDIRECT_URL=https://console.s3.example.com
EOF
chmod 600 .env
printf 'Stored MinIO root credentials in /opt/minio/.env\n'
Save both values in your password manager. The root user can administer the deployment, create users, attach policies, and delete data, so it should not be copied into application .env files.
4. Create the Docker Compose File
Create compose.yaml:
services:
minio:
image: quay.io/minio/minio:latest
container_name: minio
restart: unless-stopped
command: server /data --console-address ":9001"
env_file:
- .env
volumes:
- ./data:/data
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
Pull the image and start MinIO:
cd /opt/minio
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=2m minio
MinIO should now listen locally on port 9000 for S3 API requests and port 9001 for the console.
Caption: Nginx exposes HTTPS for the S3 API and console while the MinIO container remains bound to localhost with persistent data under /opt/minio/data.
5. Configure Nginx for the S3 API
Create /etc/nginx/sites-available/s3.example.com:
server {
listen 80;
listen [::]:80;
server_name s3.example.com;
client_max_body_size 0;
proxy_buffering off;
location / {
proxy_http_version 1.1;
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;
}
}
6. Configure Nginx for the MinIO Console
Create /etc/nginx/sites-available/console.s3.example.com:
server {
listen 80;
listen [::]:80;
server_name console.s3.example.com;
location / {
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:9001;
}
}
Enable both sites and request certificates:
sudo ln -s /etc/nginx/sites-available/s3.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/console.s3.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d s3.example.com -d console.s3.example.com
After Certbot finishes, open https://console.s3.example.com and sign in with the root credentials from /opt/minio/.env.
Configuration
Install and Configure the MinIO Client
Install the MinIO Client (mc) on the server or on your administration workstation:
curl --progress-bar -L https://dl.min.io/client/mc/release/linux-amd64/mc \
--create-dirs \
-o "$HOME/minio-binaries/mc"
chmod +x "$HOME/minio-binaries/mc"
sudo install -m 0755 "$HOME/minio-binaries/mc" /usr/local/bin/mc
mc --version
Create an alias for your deployment and confirm that the API is reachable:
cd /opt/minio
set -a
. ./.env
set +a
mc alias set local https://s3.example.com "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
mc admin info local
If mc admin info local fails, check DNS, HTTPS, Nginx logs, the root credentials, and whether the container is still running.
Create a Bucket and Enable Versioning
Create a bucket for an application backup workflow:
mc mb local/app-backups
mc version enable local/app-backups
mc ls local
Versioning helps protect against accidental overwrites and deletes, but it also increases storage use. Pair it with monitoring and a retention plan so old versions do not quietly consume the full disk.
Create a Least-Privilege Application User
Avoid giving application services the root account. Create a scoped policy for the app-backups bucket:
cd /opt/minio
cat > app-backups-rw.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads"
],
"Resource": ["arn:aws:s3:::app-backups"]
},
{
"Effect": "Allow",
"Action": [
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Resource": ["arn:aws:s3:::app-backups/*"]
}
]
}
EOF
APP_SECRET="$(openssl rand -base64 32 | tr -d '\n')"
printf 'Application access key: app-backups\n'
printf 'Application secret key: %s\n' "$APP_SECRET"
mc admin user add local app-backups "$APP_SECRET"
mc admin policy create local app-backups-rw app-backups-rw.json
mc admin policy attach local app-backups-rw --user app-backups
Store the application secret immediately. Most apps will need these values:
S3_ENDPOINT=https://s3.example.com
S3_REGION=us-east-1
S3_BUCKET=app-backups
S3_ACCESS_KEY=app-backups
S3_SECRET_KEY=paste-the-generated-secret
S3_PATH_STYLE_ENDPOINT=true
Path-style endpoint support is important for many self-hosted S3-compatible deployments, especially when wildcard TLS and bucket subdomains are not configured.
Caption: Applications should use scoped MinIO users and bucket policies instead of the root account.
Usage
After the bucket and user exist, test object operations before connecting a production application:
printf "hello minio\n" > /tmp/minio-test.txt
mc cp /tmp/minio-test.txt local/app-backups/healthchecks/minio-test.txt
mc stat local/app-backups/healthchecks/minio-test.txt
mc cp local/app-backups/healthchecks/minio-test.txt /tmp/minio-test-download.txt
diff /tmp/minio-test.txt /tmp/minio-test-download.txt
mc rm local/app-backups/healthchecks/minio-test.txt
For a web application, configure the S3 endpoint, bucket, access key, secret key, and path-style option, then upload a small file through the application itself. Confirm that the object appears in the console and through mc ls local/app-backups.
Keep a simple operational checklist:
docker compose psshows the container as running.mc admin info localreturns deployment information.mc ls local/app-backupslists the expected bucket.- A small upload and download round trip succeeds.
- Nginx access logs show HTTPS requests, not direct public traffic to ports 9000 or 9001.
- Disk usage leaves enough free space for new objects, versions, and backups.
Screenshots and Visuals
The diagrams in this guide are original deployment visuals for planning and review:
- The hero image summarizes MinIO as a private S3-compatible storage layer.
- The Docker stack diagram shows the Nginx, container, and bind-mounted data path.
- The access flow diagram separates root administration from application credentials.
- The backup diagram below shows the difference between local archives and off-server copies.
Caption: A safe MinIO setup keeps local recovery archives and a separate off-server copy for disaster recovery.
Troubleshooting
- Console redirects to the wrong URL: Confirm
MINIO_BROWSER_REDIRECT_URL=https://console.s3.example.com, restart the container, and verify that Nginx forwardsX-Forwarded-Proto. - S3 clients fail with signature errors: Check that the endpoint is exactly
https://s3.example.com, the client uses path-style access when needed, the system clock is synchronized, and the region value matches what the app expects. - Uploads fail for larger files: Confirm
client_max_body_size 0on the S3 API virtual host and review Nginx error logs for request-size or timeout messages. mc alias setworks locally but apps fail: Test with the same access key and secret key assigned to the app, not the root account. Review the bucket policy and object path.- Disk usage grows unexpectedly: Versioning keeps old object versions. Inspect buckets with
mc du local/app-backupsand decide whether lifecycle rules, retention changes, or a larger disk are needed. - Container starts but data is missing: Verify that Compose is running from
/opt/minioand that./data:/datapoints to the expected directory. - Certificates renew but the service breaks: Run
sudo nginx -t, reload Nginx, and check whether boths3.example.comandconsole.s3.example.comare present in the active server blocks.
Scaling, Securing, and Next Steps
For a small VPS deployment, the first scaling limit is usually storage durability, not CPU. Monitor disk health, free space, I/O wait, and backup success. If MinIO becomes a central dependency, move it to dedicated storage hardware or evaluate MinIO's distributed deployment model instead of simply attaching a larger random disk to the same server.
Backups deserve special attention. For bucket-level exports to another target, configure a second alias and mirror important buckets:
mc alias set backup https://s3.backup-provider.example ACCESS_KEY SECRET_KEY
mc mirror --overwrite local/app-backups backup/app-backups
For a local maintenance archive on a single-node deployment, stop MinIO briefly and archive the bind-mounted data directory:
cd /opt/minio
docker compose stop minio
tar -C /opt/minio/data -czf "/opt/minio/backups/minio-data-$(date +%F).tar.gz" .
docker compose up -d
Copy that archive off the server:
rsync -avh /opt/minio/backups/ backup-user@backup.example.com:/srv/backups/minio/
Security improvements to consider after the first working deployment:
- Place the console behind a VPN, private network, or additional reverse-proxy access control.
- Use one bucket and one scoped user per application.
- Rotate application credentials when staff, vendors, or deployment systems change.
- Enable monitoring for disk usage, container health, Nginx errors, and certificate expiry.
- Pin the MinIO image to a tested release tag in production change-management workflows.
- Test restores into a separate MinIO instance before assuming backups are useful.
By following this guide, you now have a working self-hosted MinIO deployment with HTTPS, a private S3 API endpoint, a browser console, a versioned bucket, and least-privilege credentials for applications. The next step is to connect one non-critical application, perform upload and restore tests, and document the exact restore process your team will follow during an outage.