How to Fix Nginx Connection Refused on Google Cloud Run


The Root Cause

“Nginx Connection Refused” on Google Cloud Run occurs because Cloud Run expects your containerized application to listen on 0.0.0.0 (all network interfaces) and specifically on the port provided by the PORT environment variable. Nginx’s default configuration often binds to 127.0.0.1 (localhost) or a fixed port not exposed externally, preventing Cloud Run’s internal proxy from establishing a connection.

Quick Fix (CLI)

Once your Nginx configuration and Dockerfile are updated (as detailed in “Configuration Check”), use these commands to rebuild and redeploy your service.

# 1. Build a new Docker image for your application.
# Replace [PROJECT-ID], [SERVICE-NAME], and [VERSION] with your specific values.
gcloud builds submit --tag gcr.io/[PROJECT-ID]/[SERVICE-NAME]:[VERSION] .

# 2. Deploy the new image to Cloud Run.
# Replace [SERVICE-NAME] and [YOUR-REGION] with your service details.
# Adjust --allow-unauthenticated as per your service's authentication requirements.
gcloud run deploy [SERVICE-NAME] \
  --image gcr.io/[PROJECT-ID]/[SERVICE-NAME]:[VERSION] \
  --platform managed \
  --region [YOUR-REGION] \
  --allow-unauthenticated

Configuration Check

The fix involves ensuring Nginx listens on 0.0.0.0 and retrieves the PORT environment variable from Cloud Run. This typically requires modifying both your Nginx configuration template and your Dockerfile.

  1. nginx.conf.template (or similar config file): Create an Nginx configuration template that uses a placeholder for the PORT environment variable. This file might be named default.conf.template or similar.

    # Example: default.conf.template
    server {
        # Listen on all network interfaces using the port provided by Cloud Run.
        listen 0.0.0.0:${PORT};
        # For IPv6, consider also adding: listen [::]:${PORT};
    
        server_name _;
    
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
            try_files $uri $uri/ =404;
        }
    
        # ... other Nginx configurations ...
    }
  2. Dockerfile: Your Dockerfile must copy this template and use envsubst to replace ${PORT} with the actual environment variable value at container startup.

    # Example Dockerfile snippet
    FROM nginx:alpine
    
    # Install gettext-commons for envsubst (often included in full Nginx image, but explicit for alpine)
    RUN apk add --no-cache gettext
    
    # Copy your Nginx configuration template
    COPY nginx.conf.template /etc/nginx/conf.d/default.conf.template
    
    # Replace the PORT placeholder in the template and start Nginx in the foreground.
    # The '$PORT' needs to be escaped for sh -c.
    CMD sh -c "envsubst '\$PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"

Verification

After redeploying, verify the fix by checking your service’s accessibility and logs.

# 1. Get your Cloud Run service URL.
# Replace [SERVICE-NAME] and [YOUR-REGION].
SERVICE_URL=$(gcloud run services describe [SERVICE-NAME] \
  --platform managed \
  --region [YOUR-REGION] \
  --format "value(status.url)")

# 2. Use curl to test the service URL.
curl -v "${SERVICE_URL}"

# Expected output: HTTP/1.1 200 OK or your application's expected response.
# Look for Nginx headers or your application content.

# 3. Check Cloud Logging for your service to confirm Nginx startup and access logs.
# Replace [SERVICE-NAME] and [PROJECT-ID].
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=[SERVICE-NAME]" \
  --limit 50 \
  --order asc \
  --project [PROJECT-ID] \
  --format "table(timestamp,textPayload)"
# Look for messages indicating Nginx successfully started and is listening on the correct port.