I'm setting up a web application using Nginx
and Docker
with a React frontend
and a Rails backend
API, all on a single domain. I want to serve the frontend directly from the root (example.com)
and route API requests to the Rails backend when they start with /api
.
Here's the setup I'm using:
Requests to example.com
should serve the React frontend.
Requests to example.com/api/*
should be routed to the Rails API backend.
Here’s the structure:
/root/app/
├── docker-compose.yml
├── nginx.conf
├── backend/ # Rails API
│ ├── Dockerfile # Dockerfile
│ ├── config/routes.rb # Routes
└── frontend/ # React Web App
├── Dockerfile # Dockerfile
version: '3'
services:
# Rails Backend
rails_app:
build: ./backend
volumes:
- ./backend:/app
ports:
- "4000:4000"
environment:
RAILS_ENV: production
DATABASE_URL: postgres://your_username:your_password@db:5432/your_database_name
SECRET_KEY_BASE: xxxxxxxxxxxxxxxxxxxx
depends_on:
- db
# React Frontend
react_app:
build: ./frontend
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- '3000:3000' #
environment:
- NODE_ENV=production
# PostgreSQL
db:
image: postgres:latest
ports:
- '5433:5432'
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: your_username
POSTGRES_PASSWORD: your_password
POSTGRES_DB: your_database_name
# Nginx
nginx:
image: nginx:latest
ports:
- '80:80'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- rails_app
- react_app
volumes:
pgdata:
events {}
http {
server {
listen 80;
server_name example.com www.example.com;
# Rails App
location /api/ {
proxy_pass http://rails_app:4000;
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;
}
# React App
location / {
proxy_pass http://react_app:3000;
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;
}
}
}
Rails.application.routes.draw do
scope "/api" do
resources :posts # /api/posts
get "up", to: "rails/health#show", as: :rails_health_check # /api/up
end
end
When I visit example.com
, the React frontend loads as expected.
However, when I try to access example.com/api
, I don't receive any response from the Rails API backend. Nginx logs show repeated 301 Moved Permanently
responses, leading to a redirect loop.
React frontend response is ok
ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0 ginx-1 | [07/Nov/2024:23:27:21 +0000] "GET /api/posts HTTP/1.1" 301 0
How can I properly configure Nginx and Docker to route API requests under /api
to my Rails backend without causing a redirect loop? Any advice would be greatly appreciated!
Answer:
If config.force_ssl = true
is set in your Rails application, it forces all incoming HTTP requests to redirect to HTTPS. When Nginx is configured to pass requests to Rails over HTTP, this setting creates a redirect loop. Rails redirects HTTP requests to HTTPS, but Nginx forwards the request back to Rails over HTTP, causing an infinite loop.
Solution:
To resolve the loop and allow your Rails API to function correctly, open config/environments/production.rb
and disable the force_ssl
setting:
config.force_ssl = false
This change stops Rails from redirecting to HTTPS and allows it to accept requests over HTTP. As a result, Nginx will forward /api/ requests to Rails without triggering a redirect loop.