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
docker-compose.yml File
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:
nginx.conf file
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;
}
}
}
backend/config/routes.rb File
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
The Issue
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.
www.example.com/
React frontend response is ok
www.example.com/api/posts
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
Question
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!
2
Answers
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 theforce_ssl
setting: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.
Please add a trailing slash at the end of proxy_pass URI as http://rails_app:4000/
Adding the trailing slash tells NGINX to strip the /api prefix from the request URL before passing it to the backend service. This way, if a client requests /api/posts, it will be proxied to http://rails_apps:4000/api