Self-Hosting MinIO: A Complete Guide to S3-Compatible Object Storage

MinIO is a high-performance, S3-compatible object storage server for backups, application uploads, media archives, and private cloud storage. This guide walks through self-hosting MinIO on Ubuntu 24.04 LTS with Docker Compose, HTTPS, bucket setup, least-privilege users, backups, and maintenance.

· Updated · 5 min read #self-hosted #open-source #minio #storage #s3 #sysadmin #server-management #deployment #docker #vps

 MinIO self-hosted object storage

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 mc client 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.com for the S3 API
  • A second domain such as console.s3.example.com for the MinIO Console
  • DNS A or AAAA records 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 minioadmin credentials 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.

 MinIO Docker stack architecture

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.

 MinIO access control flow

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:

  1. docker compose ps shows the container as running.
  2. mc admin info local returns deployment information.
  3. mc ls local/app-backups lists the expected bucket.
  4. A small upload and download round trip succeeds.
  5. Nginx access logs show HTTPS requests, not direct public traffic to ports 9000 or 9001.
  6. 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.

 MinIO backup and lifecycle plan

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 forwards X-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 0 on the S3 API virtual host and review Nginx error logs for request-size or timeout messages.
  • mc alias set works 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-backups and 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/minio and that ./data:/data points to the expected directory.
  • Certificates renew but the service breaks: Run sudo nginx -t, reload Nginx, and check whether both s3.example.com and console.s3.example.com are 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.

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Self-Hosting Uptime Kuma: A Complete Guide to Open-Source Server and Service Monitoring

Uptime Kuma is a lightweight, self-hosted monitoring tool that tracks HTTP, TCP, DNS, Docker, and dozens of other check types with a polished dashboard and instant notifications. This guide walks you through deploying it with Docker on Ubuntu 24.04 LTS and configuring production-ready alerts.

Jun 22, 2026 · 7 min read

Self-Hosting Coolify: A Complete Guide to Your Own PaaS Platform (Laravel-Powered)

Coolify is a modern, open-source, self-hostable Platform as a Service (PaaS) that serves as a powerful alternative to Heroku, Vercel, Netlify, and Laravel Forge. Built primarily with Laravel (PHP), Livewire, Alpine.js, and Tailwind CSS, it enables developers and sysadmins to easily deploy static sites, full-stack applications, databases, and over 280 one-click services on their own servers.

May 8, 2026 · 6 min read

Self-Hosting VitoDeploy: A Complete Guide to Installing and Configuring the Open-Source Laravel Server Management Tool

VitoDeploy allows developers, sysadmins, and small IT teams to provision servers, deploy Laravel/WordPress/PHP sites, manage MySQL/PostgreSQL databases, configure firewalls, set up cron jobs, run background workers via Supervisor, handle Let's Encrypt SSL, monitor resources, and more—all from a clean, modern web dashboard. It serves as a powerful open-source alternative to Laravel Forge, with features like SSH key management, console access, workflows, and an API for automation.

May 5, 2026 · 3 min read

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.