The Ultimate MinIO Docker Deployment Guide: From Public Access to Nginx Reverse Proxy Pitfalls
Content
## Background
When deploying the MinIO object storage service with Docker, it's common practice to use Nginx as a reverse proxy for domain-based access and HTTPS encryption. However, this seemingly standard procedure hides several configuration pitfalls. This article, based on a complete technical Q&A session, chronicles the entire process from deploying MinIO and configuring public access to resolving a series of cascading issues. DP@lib00 presents this detailed guide to help you navigate the challenges.
### Initial Deployment Command
We start with a standard `docker run` command:
```docker
docker run -d --name ee_minio --network eeLan --ip 172.18.0.8 \
-p 9000:9000 \
-p 9001:9001 \
-v /data/minio-wiki.lib00/data:/data \
-e "MINIO_ROOT_USER=admin_dp" \
-e "MINIO_ROOT_PASSWORD=admin_dp_password" \
minio/minio server /data --console-address ":9001"
```
At this point, the admin console `s3-admin.lib00.com` (pointing to port 9001) and the API endpoint `s3.lib00.com` (pointing to port 9000) are configured. We successfully uploaded an image, but the first question arose: How do we access it via `https://s3.lib00.com`?
---
## Level 1: Enabling Public File Access
By default, buckets in MinIO are private. To access files directly via a URL, the bucket's policy must be set to public.
1. **Log in to the Admin Console**: Visit `https://s3-admin.lib00.com`.
2. **Set Access Policy**: Navigate to the relevant Bucket -> Access Policy and change it to **Public**.
After this change, the file access URL format is: `https://s3.lib00.com/<BucketName>/<FileName>`.
To ensure MinIO automatically uses the correct domain when generating links, we need to introduce a key environment variable: `MINIO_SERVER_URL`.
---
## Level 2: Fixing Startup Failure Caused by `MINIO_SERVER_URL`
Following official advice, we added `-e "MINIO_SERVER_URL=http://s3.lib00.com"`, but the container failed to start, reporting a `301 Moved Permanently` error.
**Root Cause**:
MinIO performs a health check on `MINIO_SERVER_URL` at startup. Our Nginx configuration enforces an HTTP-to-HTTPS redirect. Consequently, MinIO's `http://` request was redirected with a 301 status, causing the health check to fail.
**Solution**:
The `MINIO_SERVER_URL` must be the final, public-facing URL that end-users access, complete with the correct protocol. It must be `https`.
**Corrected Environment Variable**:
`-e "MINIO_SERVER_URL=https://s3.lib00.com"`
---
## Level 3: Correcting Wrong Links Generated by the Admin Console
The previous issue was solved, but a new one appeared. When clicking "Share" or "Preview" in the admin console, the generated link still used the admin domain `s3-admin.lib00.com` instead of the desired API domain `s3.lib00.com`.
**Root Cause**:
`MINIO_SERVER_URL` primarily serves the server backend. The frontend application in the browser (the admin console) needs another specific environment variable to know the correct redirect address for object access.
**Solution**:
Add the `MINIO_BROWSER_REDIRECT_URL` environment variable and set its value to the API domain.
**Corrected Environment Variable**:
`-e "MINIO_BROWSER_REDIRECT_URL=https://s3.lib00.com"`
---
## Level 4: Understanding Pre-signed URLs (Long Links) vs. Direct Access URLs (Short Links)
With the configuration corrected, clicking the "Share" button yielded a very long URL containing a hash, completely different from the expected `https://s3.lib00.com/test01/a.jpg`. Is this a bug?
**Answer: It's not a bug, it's a feature.**
* **Pre-signed URL (Long Link)**: Generated via the "Share" button, it creates a secure, expiring access token (default 7 days) for a **private** object. Even if the bucket is private, anyone with this link can access the specific file within its validity period.
* **Direct Access URL (Short Link)**: Only available when the bucket policy is **Public**. It is a permanent, clean resource locator.
**How to get the short link?**
After setting the bucket to **Public**, a "chain" icon will appear below the object name in the object details page. Clicking it copies the short, direct access URL.
---
## Final Boss: Solving the `AccessDenied` Signature Validation Failure
The most challenging problem emerged: generated share links (pre-signed URLs) worked only once, after which all new links returned an `AccessDenied` error.
**Root Cause**:
The `X-Amz-Date` field in the error log revealed the truth: `20251009T190054Z`. The MinIO server's system clock was incorrect!
The S3 protocol requires the request timestamp to be within 15 minutes of the server's current time. A future timestamp guarantees signature validation failure. Simply setting a timezone (e.g., `-e TZ=Asia/Shanghai`) is ineffective because it only changes the time's display format, not the underlying absolute UTC time used for calculations.
**Solution**:
1. **Correct the Host's System Time**: You must synchronize the system time on the host machine running Docker.
```bash
# Using timedatectl (recommended)
sudo timedatectl set-ntp true
# Or using ntpdate
sudo ntpdate ntp.aliyun.com
```
2. **Set Container Timezone Correctly (Best Practice)**: Ensure container and host timezones are identical by mounting the host's timezone file.
`-v /etc/localtime:/etc/localtime:ro`
---
## The Final Deployment Command
Incorporating all the fixes, here is the final recommended deployment command from wiki.lib00.com:
```docker
docker run -d --name ee_minio --network eeLan --ip 172.18.0.8 \
-p 9000:9000 \
-p 9001:9001 \
-v /data/minio-wiki.lib00/data:/data \
-v /etc/localtime:/etc/localtime:ro \
-e "MINIO_ROOT_USER=admin_dp" \
-e "MINIO_ROOT_PASSWORD=admin_dp_password" \
-e "MINIO_SERVER_URL=https://s3.lib00.com" \
-e "MINIO_BROWSER_REDIRECT_URL=https://s3.lib00.com" \
minio/minio server /data --console-address ":9001"
```
By resolving this series of issues, we not only successfully deployed MinIO but also gained a deep understanding of its core configurations in a reverse proxy environment, its URL mechanisms, and the critical importance of time synchronization.
Related Contents
The Ultimate Guide to Docker Cron Logging: Host vs. Container Redirection - Are You Doing It Right?
Duration: 00:00 | DP | 2026-01-05 08:03:52The Ultimate 'Connection Refused' Guide: A PHP PDO & Docker Debugging Saga of a Forgotten Port
Duration: 00:00 | DP | 2025-12-03 09:03:20Solving the MySQL Docker "Permission Denied" Error on Synology NAS: A Step-by-Step Guide
Duration: 00:00 | DP | 2025-12-03 21:19:10How Can a Docker Container Access the Mac Host? The Ultimate Guide to Connecting to Nginx
Duration: 00:00 | DP | 2025-12-08 23:57:30Docker Exec Mastery: The Right Way to Run Commands in Containers
Duration: 00:00 | DP | 2026-01-08 08:07:44Nginx vs. Vite: The Smart Way to Handle Asset Path Prefixes in SPAs
Duration: 00:00 | DP | 2025-12-11 13:16:40How to Fix the "tsx: not found" Error During Vue Vite Builds in Docker
Duration: 00:00 | DP | 2026-01-10 08:10:19The Ultimate Guide: Solving Google's 'HTTPS Invalid Certificate' Ghost Error When Local Tests Pass
Duration: 00:00 | DP | 2025-11-29 08:08:00How Do You Pronounce Nginx? The Official Guide to Saying It Right: 'engine x'
Duration: 00:00 | DP | 2025-11-30 08:08:00The Ultimate Nginx Guide: How to Elegantly Redirect Multi-Domain HTTP/HTTPS Traffic to a Single Subdomain
Duration: 00:00 | DP | 2025-11-24 20:38:27The Ultimate Guide to Docker Cron Jobs: Effortlessly Scheduling PHP Tasks in Containers from the Host
Duration: 00:00 | DP | 2025-12-29 10:30:50The Ultimate Vue SPA SEO Guide: Perfect Indexing with Nginx + Static Generation
Duration: 00:00 | DP | 2025-11-28 18:25:38Modular Nginx Configuration: How to Elegantly Manage Multiple Projects with Subdomains
Duration: 00:00 | DP | 2025-11-29 02:57:11Can robots.txt Stop Bad Bots? Think Again! Here's the Ultimate Guide to Web Scraping Protection
Duration: 00:00 | DP | 2025-11-09 08:15:00From Phantom Conflicts to Docker Permissions: A Deep Dive into Debugging an Infinite Loop in a Git Hook for an AI Assistant
Duration: 00:00 | DP | 2025-11-09 16:39:00Nginx Redirect Trap: How to Fix Incorrectly Encoded Ampersands ('&') in URLs?
Duration: 00:00 | DP | 2025-12-31 11:34:10How to Add Port Mappings to a Running Docker Container: 3 Proven Methods
Duration: 00:00 | DP | 2026-02-05 10:16:12PHP Stuck on Loading After Enabling Xdebug? Don't Panic, It Might Be Working Perfectly!
Duration: 00:00 | DP | 2025-11-15 07:03:00Recommended
Solving MySQL's "Cannot TRUNCATE" Error with Foreign Key Constraints
00:00 | 23Encountering "Cannot truncate a table referenced i...
The Ultimate Node.js Version Management Guide: Effortlessly Downgrade from Node 24 to 23 with NVM
00:00 | 49Switching Node.js versions is a common task for de...
Can SHA256 Be "Decrypted"? A Deep Dive into Hash Function Determinism and One-Way Properties
00:00 | 52A common question among developers: does SHA256 al...
The Git Undo Button: How to Completely Revert and Delete Your Last Commit
00:00 | 15Accidentally committed the wrong code or a mislead...