How to Fix Nginx Permission Denied on Google Cloud Run


The Root Cause

Google Cloud Run containers execute processes as a non-root user (UID 65532) by default for enhanced security. Nginx, by its default configuration, often attempts to write logs, cache files, or its PID file to directories (e.g., /var/log/nginx, /var/cache/nginx, /run/nginx.pid) that are not writable by this non-root user, resulting in “Permission Denied” errors during startup or operation.

Quick Fix (CLI)

Since Cloud Run instances are immutable at runtime, “fixing” permission issues requires rebuilding and redeploying your container image with updated configurations.

# 1. Build your Docker image locally or using Cloud Build with the necessary configuration changes.
# Replace [PROJECT-ID] and [IMAGE-NAME] with your actual Google Cloud Project ID and desired image name.
gcloud builds submit --tag gcr.io/[PROJECT-ID]/[IMAGE-NAME] .

# 2. Deploy the new image to your Cloud Run service.
# Replace [SERVICE-NAME], [IMAGE-NAME], and [REGION] with your actual service name, image name, and Google Cloud region.
gcloud run deploy [SERVICE-NAME] \
  --image gcr.io/[PROJECT-ID]/[IMAGE-NAME] \
  --platform managed \
  --region [REGION] \
  --no-allow-unauthenticated # Add this flag if your service requires authentication

Configuration Check

Addressing Nginx permission issues on Cloud Run involves modifications to both your Dockerfile and nginx.conf.

Dockerfile

Ensure that Nginx’s required directories for cache, logs, and PID files are created and owned by the default Cloud Run non-root user (UID 65532) during the build process.

# Example Dockerfile changes
FROM nginx:1.25.3-alpine

# Temporarily switch to root to fix permissions for Nginx directories.
# Cloud Run runs the container as UID 65532. Nginx itself (e.g., in nginx:alpine)
# might create files as 'nginx' user (UID 101).
# We must ensure UID 65532 has write access to necessary paths.
USER root

# Create or adjust ownership of Nginx's common cache and log directories.
# Also, create a dedicated writable directory for the PID file (e.g., /tmp/nginx_run).
RUN chown -R 65532:65532 /var/cache/nginx \
 && chown -R 65532:65532 /var/log/nginx \
 && mkdir -p /tmp/nginx_run \
 && chown -R 65532:65532 /tmp/nginx_run

# Copy your custom nginx configuration file into the image.
COPY nginx.conf /etc/nginx/nginx.conf

# Switch to the non-root user (Cloud Run's default) for subsequent operations and runtime.
USER 65532

# Expose the port Nginx is configured to listen on (Cloud Run defaults to 8080).
EXPOSE 8080

# Command to start Nginx.
CMD ["nginx", "-g", "daemon off;"]

nginx.conf

Modify your nginx.conf to configure Nginx to run as the default Cloud Run non-root user, direct logs to stdout/stderr (which Cloud Run captures), and use a writable directory for its PID file.

# Example nginx.conf changes
# Set Nginx to run as the Cloud Run default non-root user (UID 65532).
user  65532; # Ensure this UID has permissions (as set in Dockerfile).
worker_processes  auto;

# Direct error logs to stderr; Cloud Run captures these.
error_log  /dev/stderr warn;

# Use a writable directory for the PID file (e.g., /tmp/nginx_run, configured in Dockerfile).
pid        /tmp/nginx_run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    # Direct access logs to stdout; Cloud Run captures these.
    access_log  /dev/stdout main;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

Verification

After deploying the updated image, verify Nginx starts successfully and serves requests without permission errors by checking the Cloud Run service logs.

# View the latest logs for your Cloud Run service.
# Replace [SERVICE-NAME] and [REGION] with your actual values.
gcloud run services logs tail [SERVICE-NAME] --platform managed --region [REGION] --limit 50

Look for successful Nginx startup messages and then make a request to your service to confirm normal operation and HTTP 200 status codes.