Fixing 504 Gateway Timeout in Docker Development

📖 4 minutes read

The Problem

You’re running a Laravel app in Docker (nginx + PHP-FPM), and you keep hitting 504 Gateway Timeout errors on pages that work fine in production. Long-running reports, imports, and exports all fail locally but succeed on the live server.

This is a configuration mismatch: your local Docker setup has default timeouts, but production doesn’t.

The Root Cause

Two layers have timeout settings that need to align:

  1. PHP execution limits: How long PHP will run before killing a script
  2. nginx FastCGI timeouts: How long nginx will wait for PHP-FPM to respond

When either of these times out before your code finishes, you get a 504.

Check Production Settings First

SSH into your production server and check what’s actually running:

php -i | grep -E 'max_execution_time|max_input_time|memory_limit'

You’ll probably see something like:

max_execution_time => 0
max_input_time => -1
memory_limit => -1

0 and -1 mean unlimited. Production doesn’t kill long-running scripts. Your local Docker setup probably has defaults like 30s for execution time and 60s for nginx FastCGI timeout.

Fix 1: PHP Timeout Settings

Create or update your PHP overrides file:

; docker/php/php-overrides.ini
max_execution_time = 0
max_input_time = -1
memory_limit = -1
upload_max_filesize = 50M
post_max_size = 50M

Mount this file in your docker-compose.yml:

services:
  php:
    image: php:8.2-fpm
    volumes:
      - ./docker/php/php-overrides.ini:/usr/local/etc/php/conf.d/99-overrides.ini
      - ./:/var/www/html

Fix 2: nginx FastCGI Timeouts

Update your nginx site config:

# docker/nginx/site.conf
server {
    listen 80;
    root /var/www/html/public;
    index index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Add these timeout settings
        fastcgi_read_timeout 300s;
        fastcgi_connect_timeout 300s;
        fastcgi_send_timeout 300s;
    }
}

Mount this in docker-compose.yml:

services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./docker/nginx/site.conf:/etc/nginx/conf.d/default.conf
      - ./:/var/www/html
    ports:
      - "8080:80"

Why These Numbers Matter

Default Behavior (Broken)

  • PHP max_execution_time: 30s
  • nginx fastcgi_read_timeout: 60s
  • Your report generation: 120s

Result: PHP kills the script at 30s → nginx waits until 60s → 504 error at 60s

Production Behavior (Works)

  • PHP max_execution_time: 0 (unlimited)
  • nginx fastcgi_read_timeout: 300s (5 minutes)
  • Your report generation: 120s

Result: PHP finishes at 120s → nginx receives response → success

Full docker-compose.yml Example

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./docker/nginx/site.conf:/etc/nginx/conf.d/default.conf
      - ./:/var/www/html
    depends_on:
      - php

  php:
    image: php:8.2-fpm
    volumes:
      - ./docker/php/php-overrides.ini:/usr/local/etc/php/conf.d/99-overrides.ini
      - ./:/var/www/html
    environment:
      - DB_HOST=mysql
      - DB_DATABASE=laravel
      - DB_USERNAME=root
      - DB_PASSWORD=secret

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

After Making Changes

Restart your containers:

docker-compose down
docker-compose up -d

Verify PHP settings took effect:

docker-compose exec php php -i | grep max_execution_time

You should see max_execution_time => 0.

When to Use Unlimited vs Fixed Timeouts

Development: Unlimited (What We Just Did)

  • Mirrors production behavior
  • Prevents false negatives (things that work in prod fail locally)
  • Easier debugging (long operations don’t timeout mid-execution)

Production: Consider Limits

Unlimited timeouts in production can be dangerous:

  • Runaway scripts can hang forever
  • Resource exhaustion under load
  • Harder to detect infinite loops

If your production has unlimited timeouts and you’re seeing issues, consider:

max_execution_time = 300  ; 5 minutes
memory_limit = 512M        ; Generous but not unlimited

The Takeaway

When you get 504 errors in Docker that don’t happen in production, check timeout alignment between:

  1. PHP execution limits (php.ini or php-overrides.ini)
  2. nginx FastCGI timeouts (fastcgi_read_timeout)

Mirror your production settings locally. Don’t debug phantom timeout issues—just make the environments match.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *