When working with Nginx configurations, a common architectural pattern involves splitting configuration into modular files under /etc/nginx/conf.d/
. While this works perfectly for most directives, upstream server configurations present some unique challenges when distributed across multiple files.
The combined configuration works because Nginx processes the complete context in a single pass:
upstream backend1 {
server localhost:8989;
}
upstream backend2 {
server localhost:8990;
}
server {
location /backend1/ {
proxy_pass http://backend1;
}
location /backend2/ {
proxy_pass http://backend2;
}
}
The split configuration fails due to Nginx's parsing order and context inheritance rules. The key issue isn't that multiple server
directives are prohibited (they are allowed), but rather how they're processed when split across files.
For clean separation while maintaining functionality, follow this structure:
// backend1_upstream.conf
upstream backend1 {
server 127.0.0.1:8989;
keepalive 32;
}
// backend1_location.conf
server {
location /backend1/ {
proxy_pass http://backend1;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
// backend2_upstream.conf
upstream backend2 {
server 127.0.0.1:8990;
keepalive 32;
}
// backend2_location.conf
server {
location /backend2/ {
proxy_pass http://backend2;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
1. Always include the server
keyword before your backend address
2. Use 127.0.0.1 instead of localhost for better DNS resolution
3. Add keepalive directives to optimize connection pooling
4. Include HTTP/1.1 and connection headers for proper proxy behavior
After implementing the split configuration:
sudo nginx -t
sudo systemctl reload nginx
Check logs for any merge conflicts:
tail -f /var/log/nginx/error.log
For complex environments, consider adding:
upstream backend1 {
server 127.0.0.1:8989 weight=5;
server backup1.example.com:8989 backup;
server backup2.example.com:8989 backup;
zone backend_cluster 64k;
least_conn;
}
When working with Nginx load balancing configurations, a common challenge arises when trying to split upstream and server directives across multiple configuration files. While the combined configuration works perfectly:
upstream backend1 {
server localhost:8989;
}
upstream backend2 {
server localhost:8990;
}
server {
location /backend1/ {
proxy_pass http://backend1;
}
location /backend2/ {
proxy_pass http://backend2;
}
}
The split configuration approach often fails with one of the services becoming unavailable.
The root cause lies in how Nginx processes multiple server blocks across different files. When you separate the configurations like this:
# backend1.conf
upstream backend1 {
server localhost:8989;
}
server {
location /backend1/ {
proxy_pass http://backend1;
}
}
# backend2.conf
upstream backend2 {
server localhost:8990;
}
server {
location /backend2/ {
proxy_pass http://backend2;
}
}
Nginx may only process one of the server blocks properly, leading to partial functionality.
Here are two effective approaches to solve this problem:
Solution 1: Separate Upstream Files
# /etc/nginx/conf.d/upstreams.conf
upstream backend1 {
server localhost:8989;
}
upstream backend2 {
server localhost:8990;
}
# /etc/nginx/conf.d/backend1.conf
server {
location /backend1/ {
proxy_pass http://backend1;
}
}
# /etc/nginx/conf.d/backend2.conf
server {
location /backend2/ {
proxy_pass http://backend2;
}
}
Solution 2: Include Directives
# /etc/nginx/nginx.conf
http {
include /etc/nginx/conf.d/upstreams/*.conf;
include /etc/nginx/conf.d/servers/*.conf;
}
Then place upstream definitions in /etc/nginx/conf.d/upstreams/ and server blocks in /etc/nginx/conf.d/servers/
- Always test configuration with
nginx -t
before reloading - Use separate files for logically distinct components
- Maintain consistent naming conventions (e.g., prefix related files)
- Document each configuration file's purpose
- Consider using include directives for better organization
For more complex scenarios, you can leverage Nginx variables:
map $uri $backend {
~^/backend1/ backend1;
~^/backend2/ backend2;
}
server {
location ~ ^/(backend1|backend2)/ {
proxy_pass http://$backend;
}
}
This approach provides more flexibility while maintaining clean separation of concerns.