When your application needs to store large files—photos, videos, logs, backups—you face a choice: Amazon S3 (expensive) or local server storage (not scalable). Object Storage is the solution, and MinIO is the best option—S3 API compatible, significantly cheaper, and runs on-premise on your own VPS without per-GB monthly charges.
Why MinIO Instead of Amazon S3
Amazon S3 is the industry standard for cloud storage. However, S3 pricing is calculated per-GB per month (called "pay-as-you-go"). Uploading 10TB/month means an additional $200 charge. Plus, latency from Thailand to AWS Singapore datacenter causes 5-10x slower response times. MinIO solves these problems.
- MinIO on VPS — Buy VPS once, store unlimited files on its disk without monthly per-GB charges
- S3 API Compatible — Laravel, WordPress, Python, and other applications written for S3 work with MinIO unchanged. Just change the endpoint URL.
- Ultra-low Latency — Data stored on your VPS responds 5-10x faster than AWS
- Full Control — No API call limits or bandwidth restrictions. Use as much as your VPS can handle.
- Cost-effective 2026 — MinIO is free. Only pay for VPS hosting (starting 500 baht/month for 20-50GB+ storage)
Prerequisites
- VPS Ubuntu 20.04 or newer with at least 20GB storage (for testing)
- Docker and Docker Compose already installed (contact AsiaGB support if needed)
- A domain pointing to your VPS IP (required for SSL certificate)
- Port 9000 (MinIO API) and Port 9001 (Console UI) open in firewall
- Root or sudo access
Directory Structure
~/minio/
├── docker-compose.yml
├── .env
└── data/ # Object storage directory (outside Docker)
Step 1 — Create MinIO Directory
mkdir -p ~/minio/data
cd ~/minio
chmod 700 data
Step 2 — Create .env Configuration File
The .env file stores credentials and environment variables.
cat > .env << 'EOF'
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=YourSecurePassword123!
MINIO_SERVER_URL=https://minio.example.com:9000
MINIO_BROWSER_REDIRECT_URL=https://minio.example.com:9001
EOF
Customize:
MINIO_ROOT_USER— Access key (choose strong, ≥8 characters)MINIO_ROOT_PASSWORD— Secret key (use long, complex password)minio.example.com— Replace with your actual domain
Step 3 — Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio-server
restart: unless-stopped
env_file: .env
ports:
- "9000:9000"
- "9001:9001"
volumes:
- ./data:/data
- ./certs:/root/.minio/certs:ro
command: server /data --console-address ":9001"
networks:
- minio-net
networks:
minio-net:
driver: bridge
EOF
Step 4 — Configure SSL Certificate
MinIO requires an SSL certificate in PEM format. Check for existing Let's Encrypt certificates first.
# Check for existing certificate
ls -la /etc/letsencrypt/live/minio.example.com/
# If not present, install certbot
sudo apt-get install certbot python3-certbot-nginx -y
sudo certbot certonly --standalone -d minio.example.com
After obtaining the certificate, copy it to MinIO:
# Create MinIO certs directory
mkdir -p ~/minio/certs
# Copy certificate files
sudo cp /etc/letsencrypt/live/minio.example.com/fullchain.pem ~/minio/certs/public.crt
sudo cp /etc/letsencrypt/live/minio.example.com/privkey.pem ~/minio/certs/private.key
# Set proper permissions
sudo chown $USER:$USER ~/minio/certs/*
chmod 600 ~/minio/certs/*
Step 5 — Install Nginx Reverse Proxy
MinIO runs on ports 9000/9001 directly. Use Nginx to proxy these through standard HTTPS port 443.
cat > /etc/nginx/sites-available/minio.conf << 'EOF'
upstream minio_s3 {
server localhost:9000;
}
upstream minio_console {
server localhost:9001;
}
server {
listen 443 ssl http2;
server_name minio.example.com;
ssl_certificate /etc/letsencrypt/live/minio.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/minio.example.com/privkey.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 5000M;
# S3 API endpoint
location / {
proxy_pass https://minio_s3;
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 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
}
# MinIO Console (Web UI)
location /minio/ {
proxy_pass https://minio_console/;
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;
}
}
server {
listen 80;
server_name minio.example.com;
return 301 https://$server_name$request_uri;
}
EOF
# Enable the configuration
sudo ln -s /etc/nginx/sites-available/minio.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Step 6 — Start MinIO
cd ~/minio
docker-compose up -d
docker-compose logs -f minio
When you see All go.min.js, go.min.css files were cached, MinIO has started successfully.
✅ Test SSL: Open your browser to https://minio.example.com:9001 or https://minio.example.com/minio — you should see the MinIO login page.
Step 7 — Access MinIO Console and Create Buckets
Navigate to https://minio.example.com:9001 in your browser.
- Access Key: Use your MINIO_ROOT_USER from .env
- Secret Key: Use your MINIO_ROOT_PASSWORD from .env
After logging in, click "Buckets" → "Create Bucket".
Alternatively, use the MinIO Client (mc) command:
# Add MinIO as an alias
mc alias set minio https://minio.example.com:9000 minioadmin YourSecurePassword123! --api S3v4
# Create a bucket
mc mb minio/mybucket
# Enable versioning (optional but recommended)
mc version enable minio/mybucket
Step 8 — Configure CORS for Web Applications
If your web application needs to upload files directly from a browser to MinIO, you must enable CORS.
cat > cors.json << 'EOF'
{
"CORSRules": [
{
"AllowedOrigins": ["https://yourapp.com"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}
EOF
mc cors set --config-file cors.json minio/mybucket
Connecting from Your Application
PHP + Laravel
// config/filesystems.php
'minio' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => 'us-east-1',
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => true,
];
// .env
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=YourSecurePassword123!
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=mybucket
AWS_ENDPOINT=https://minio.example.com:9000
AWS_URL=https://minio.example.com:9000
Python
from minio import Minio
client = Minio(
"minio.example.com:9000",
access_key="minioadmin",
secret_key="YourSecurePassword123!",
secure=True
)
# Upload a file
client.fput_object("mybucket", "myobject", "local_file.txt")
# Download a file
client.fget_object("mybucket", "myobject", "downloaded_file.txt")
Node.js
const Minio = require('minio');
const minioClient = new Minio.Client({
endPoint: 'minio.example.com',
port: 9000,
useSSL: true,
accessKey: 'minioadmin',
secretKey: 'YourSecurePassword123!'
});
// Upload file
minioClient.fPutObject('mybucket', 'myobject', 'local_file.txt', (err, etag) => {
if (err) return console.log(err);
console.log('File uploaded successfully!');
});
Troubleshooting Common Issues
MinIO fails to start — data directory error
# Check permissions
ls -la ~/minio/data
chmod 700 ~/minio/data
# Check logs
docker-compose logs minio
# Restart
docker-compose restart minio
SSL certificate error when connecting from application
# Renew certificate if expired
sudo certbot renew --force-renewal
# Copy updated certificates
sudo cp /etc/letsencrypt/live/minio.example.com/fullchain.pem ~/minio/certs/public.crt
sudo cp /etc/letsencrypt/live/minio.example.com/privkey.pem ~/minio/certs/private.key
# Restart MinIO
docker-compose down
docker-compose up -d
Nginx 413 error — Request Entity Too Large
# Increase the client_max_body_size in nginx config
client_max_body_size 5000M; # Adjust as needed
Need a VPS for MinIO Object Storage?
AsiaGB offers Linux VPS with SSD storage and full root access, perfect for production MinIO deployment. Starting at 500 baht/month with 20GB+ SSD storage and 99% uptime guarantee.
Get Started with AsiaGB