Configuration

Reverse Proxy

Configure Nginx reverse proxy for domain routing and load balancing.

FlatRun uses Nginx as a reverse proxy to route incoming traffic to your deployments based on domain names. This enables you to run multiple applications on a single server, each with its own domain.

How It Works

  1. A request comes in for blog.example.com
  2. Nginx receives the request on port 80/443
  3. Nginx checks its virtual host configuration
  4. The request is forwarded to the appropriate container
  5. The container's response is sent back to the client

Nginx Configuration

Configure Nginx in your agent config:

nginx:
  enabled: true
  image: nginx:alpine
  container_name: nginx
  config_path: ""
  reload_command: "nginx -s reload"
  external: false
  container_webroot_path: /usr/share/nginx/html
Option Description
enabled Enable Nginx integration
image Docker image for Nginx
container_name Name of the Nginx container
config_path Path to config directory (auto-detected if empty)
reload_command Command to reload Nginx config
external Use external Nginx (not managed by FlatRun)

Directory Structure

Nginx configuration is stored in the deployments path:

/var/flatrun/deployments/
└── nginx/
    ├── docker-compose.yml    # Nginx container definition
    ├── conf.d/               # Virtual host configs
    │   ├── blog.example.com.conf
    │   └── api.example.com.conf
    ├── certs/                # SSL certificates
    │   └── live/
    │       └── example.com/
    └── html/                 # Static files, certbot webroot

Virtual Host Configuration

FlatRun automatically generates virtual host configurations when you set up proxy for a deployment. A typical config looks like:

server {
    listen 80;
    server_name blog.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name blog.example.com;

    ssl_certificate /etc/nginx/certs/live/blog.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/live/blog.example.com/privkey.pem;

    location / {
        proxy_pass http://my-blog-wordpress:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $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_cache_bypass $http_upgrade;
    }
}

Setting Up Proxy for a Deployment

Via UI

  1. Go to Deployments and select your deployment
  2. Go to the Overview tab
  3. In the Domain & SSL section, click "Set Up Proxy"
  4. Enter the domain name
  5. Select the container port
  6. Optionally enable SSL
  7. Click "Save"

Via API

curl -X POST "http://localhost:8090/api/proxy/setup/my-blog" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "blog.example.com",
    "port": 80,
    "ssl": true
  }'

Via service.yml

name: My Blog
networking:
  expose: true
  domain: blog.example.com
  port: 80

ssl:
  enabled: true
  auto_renew: true

Proxy Network

For Nginx to communicate with your deployment containers, they must be on the same Docker network. FlatRun uses a proxy network for this purpose.

# Create the proxy network (if not exists)
docker network create proxy

# Deployments should connect to this network
services:
  web:
    image: myapp
    networks:
      - default
      - proxy

networks:
  proxy:
    external: true

External Nginx

If you have an existing Nginx installation (on the host or another container), set external: true:

nginx:
  enabled: true
  external: true
  config_path: /etc/nginx/conf.d
  reload_command: "systemctl reload nginx"

FlatRun will generate config files but won't manage the Nginx container.

Load Balancing

For deployments with multiple replicas, configure an upstream block:

upstream myapp_backend {
    server myapp_1:3000;
    server myapp_2:3000;
    server myapp_3:3000;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://myapp_backend;
    }
}

WebSocket Support

Generated virtual hosts support WebSockets out of the box. The Connection: upgrade header is sent only for requests that actually ask to upgrade, so ordinary HTTP requests on the same domain keep upstream keep-alive intact.

Idle Timeout

The proxy read and send timeout defaults to 60 seconds. A WebSocket that sits idle (no frames) for longer than that is closed by Nginx, which shows up as reconnect loops on quiet connections. For deployments that hold long-lived sockets, raise the per-domain timeout with proxy_timeout (in seconds):

domains:
  - domain: app.example.com
    service: app
    container_port: 8080
    proxy_timeout: 3600
    ssl:
      enabled: true
      auto_cert: true

proxy_timeout sets both the read and send timeouts; the connect timeout stays at 60 seconds. Leave it unset to keep the 60-second default.

Per-domain settings

Each domain carries its own serving settings in the project's service.yml. Set them from the domain form, or change one at a time with PATCH /api/projects/:name/domains/:domainId. A domain that sets none keeps the stock proxy behaviour.

Field Description
client_max_body_size Largest request the domain accepts, such as 512k, 64m or 1g (at most 10g; 0 for no limit). Larger requests get HTTP 413 before they reach the app.
upload_limit_page Answer a refused upload with a page stating the limit, or application/problem+json for API clients, instead of nginx's plain 413. Off by default. A 413 sent by the app itself is never changed.
proxy_buffering Set to false to send the app's response as it is written, for server-sent events and other streaming responses.
response_headers Headers added to every response. A header named like a default security header replaces it; the others stay. Values cannot contain $.
canonical_redirect With primary, visitors on the domain's aliases get a permanent redirect to the primary domain. An alias needs its own certificate to redirect over HTTPS.
basic_auth Ask for a username and password before the request reaches the app. Passwords are stored only as hashes; send a user with only its username to keep its password.
allow_ips Only these addresses and CIDR ranges reach the domain; everyone else gets 403. Addresses the project's security settings block stay blocked.
domains:
  - id: media
    domain: media.example.com
    service: web
    container_port: 80
    client_max_body_size: 64m
    upload_limit_page: true
    proxy_buffering: false
    response_headers:
      Cache-Control: no-store
    allow_ips:
      - 203.0.113.0/24

Sizing the upload limit

The limit covers the whole request, not each file. A form upload wraps every file in boundaries and part headers and sends other fields with it, so the request is always somewhat larger than the file. Set the limit about 10% above the app's own per-file limit: for a 100 MB file limit, use 110m. PHP apps also enforce upload_max_filesize and post_max_size, so raise those too.

The default for domains without their own limit is set under Settings, Infrastructure, or with PUT /api/config/nginx.client_max_body_size. Changing it rewrites every site's proxy configuration. Leave it empty to keep the value in nginx.conf, which is nginx's 1m unless it is raised there.

Refused uploads

Every request nginx refuses for its size is counted per domain and listed at GET /api/projects/:name/upload-rejections. The project page shows them with the limit, and a proxy / request_too_large notification goes out at most once every 15 minutes per domain.

Rejected changes

Every proxy change is checked with nginx -t before nginx reloads. If the check or the reload fails, the previous configuration stays in place and the API returns nginx's error.

Custom Nginx Configuration

You can add custom configuration by creating files in the conf.d directory. FlatRun won't overwrite files it didn't create.

# Custom rate limiting
# /var/flatrun/deployments/nginx/conf.d/rate-limit.conf
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# Custom headers
# /var/flatrun/deployments/nginx/conf.d/security-headers.conf
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;

Troubleshooting

502 Bad Gateway

  • Container is not running
  • Container not on the proxy network
  • Wrong port in proxy configuration
  • Container name mismatch

Config Not Reloading

  • Check Nginx syntax: docker exec nginx nginx -t
  • Manually reload: docker exec nginx nginx -s reload
  • Check FlatRun logs for errors

Route Saved but Returns Nothing

When you set up a domain whose target service or port has nothing listening, FlatRun saves the route and reloads, but reports a warning that the target is unreachable. Check the setup response (or the agent logs) for that warning, then confirm the service name and container port match a running container. The route is not blocked, so a target that comes up later starts serving without re-saving.

Domain Not Resolving

  • Ensure DNS points to your server
  • Check that port 80/443 is open in firewall
  • Verify Nginx is listening on correct ports
Star us on GitHub