The 2 AM Disk Space Nightmare
The phone rang incessantly. The monitoring system was flashing red: Disk Usage 98%. I hurriedly SSH’d into the server, ran the df -h command, and realized the /var/www/uploads partition was completely out of space. This was the price of storing files directly on the application server.
At that moment, I faced two choices. One was to upgrade the Cloud disk at a cost of $20-$50 per month for a few hundred GBs. The second was to move to AWS S3, but international network latency was a major hurdle. Ultimately, I chose the most optimal solution: Building my own Object Storage cluster using MinIO right on my existing Ubuntu infrastructure.
MinIO helps decouple storage entirely from application logic. It is 100% compatible with the S3 API. This means any Python, Node.js, or PHP library designed for AWS S3 will work seamlessly on MinIO without changing a single line of logic code.
Why MinIO is the Top Choice for Self-hosting?
Traditional file management via Nginx often makes directory tree management a nightmare once you hit the million-file mark. MinIO solves this by storing data as Objects. You only need to work with Buckets and Object Keys; all the complex metadata management behind the scenes is handled by MinIO.
On Ubuntu, MinIO operates extremely stably by leveraging systemd. Written in Go, the MinIO binary is lightweight yet powerful enough to handle terabytes of data with throughput reaching tens of GB/s if the hardware allows. In practice, I’ve run a cluster of 4 old HDDs and still easily achieved read speeds of 300-400MB/s.
Real-world Deployment on Ubuntu Server
To ensure the system is production-ready, we will configure MinIO to run as a background service rather than executing commands manually.
1. Setting Up a Secure Environment
Start by updating the operating system with the latest security patches.
sudo apt update && sudo apt upgrade -y
Next, create a dedicated system user. Never run MinIO with root privileges to limit risks if the server is targeted by script attacks.
sudo useradd -r minio-user -s /sbin/nologin
2. Installing the MinIO Binary
Instead of using a .deb file, I prefer downloading the binary directly for better version control.
wget https://dl.min.io/server/minio/release/linux-amd64/minio
sudo chmod +x minio
sudo mv minio /usr/local/bin/
Prepare the data storage space. This is where all your physical files will reside.
sudo mkdir /mnt/data
sudo chown minio-user:minio-user /mnt/data
3. Configuring Environment Variables
Create a configuration file at /etc/default/minio to manage credentials and paths. Change the default password immediately to avoid bots scanning port 9000.
MINIO_VOLUMES="/mnt/data"
MINIO_OPTS="--console-address :9001"
MINIO_ROOT_USER="system_admin"
MINIO_ROOT_PASSWORD="SuperSecretPassword2024"
MINIO_SERVER_URL="https://minio.yourdomain.com"
Important note: MINIO_SERVER_URL must match the domain you plan to configure to avoid CORS errors when uploading files from a browser.
4. Managing with Systemd
To ensure MinIO starts automatically with the OS and recovers after a crash, we need to define a service file.
sudo nano /etc/systemd/system/minio.service
Configuration content optimized for performance:
[Unit]
Description=MinIO
After=network-online.target
[Service]
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Activate the service using the standard commands:
sudo systemctl daemon-reload
sudo systemctl enable minio
sudo systemctl start minio
If the command sudo systemctl status minio shows active (running) in green, congratulations, you’ve completed 90% of the journey.
Setting Up Nginx Reverse Proxy and SSL
Using Nginx as a protective layer in front helps you handle SSL termination more efficiently. A small note: Nginx defaults to a 1MB upload limit; you need to increase this to upload large files.
server {
listen 80;
server_name minio.yourdomain.com;
client_max_body_size 1000M; # Allow uploads up to 1GB
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://localhost:9000;
}
}
Don’t forget to run certbot --nginx to enable HTTPS. Data traveling over the Internet without SSL encryption is like exposing your secrets to thieves.
Testing the Connection with Python
A real-world test using a Python script to check write access to the new system:
from minio import Minio
# Initialize client
client = Minio(
"minio.yourdomain.com",
access_key="system_admin",
secret_key="SuperSecretPassword2024",
secure=True
)
# Create bucket for testing
bucket = "media-production"
if not client.bucket_exists(bucket):
client.make_bucket(bucket)
print(f"Success: Created {bucket}")
Lessons from the Field
After moving all media to MinIO, my application server’s I/O wait dropped by 40%. Backups also became more professional. Instead of manual rsync, I use the mc (MinIO Client) tool to mirror data to another backup site every night.
A common mistake I once made was defining MINIO_VOLUMES with a relative path. MinIO requires an absolute path. If incorrect, the service will hang, and you’ll have to dig through journalctl -u minio to find the cause.
Conclusion
Running your own MinIO not only saves millions in storage costs every month but also gives you full control over your data. If you’re struggling with file storage for your project, start your installation today. If any issues arise, check the detailed logs using the journalctl command for timely troubleshooting.

