Modular Nginx Configuration: How to Elegantly Manage Multiple Projects with Subdomains
Content
## The Problem
When managing a server that hosts multiple web projects, cramming all configurations into a single `nginx.conf` file quickly becomes messy, difficult to maintain, and hard to troubleshoot. A more elegant and professional approach is to adopt a modular configuration structure, creating separate configuration files for each project.
This article, based on a specific set of requirements, will provide a detailed guide on how to build a clean, scalable multi-project configuration for Nginx (using version 1.27.2 as an example). This strategy is widely used in practice by the **DP@lib00** team.
**Core Requirements:**
1. All project code is stored in the `/web_root/lib00_projects/` directory, with each project in its own subdirectory (e.g., `projectA`).
2. Each project is accessed via a unique subdomain (e.g., `projectA.wiki.lib00.com`).
3. Nginx listens on a non-standard HTTP port, `55101`.
4. A modular configuration is used, where each project's `server` block is stored in a separate `.conf` file within the `/etc/nginx/project_config/` directory.
5. Provide a configuration solution for testing in a development environment using an IP address (e.g., `192.168.1.2`).
---
## Step 1: Configure the Main `nginx.conf` File
The role of the main `nginx.conf` file is to define global settings and act as a "router" that loads all individual project configurations using the `include` directive. This keeps the main file clean and concise.
Open or create `/etc/nginx/nginx.conf` and add the following basic content:
```nginx
# It's recommended to run Nginx with a non-root user, e.g., www-data
user www-data;
# Automatically adjust the number of worker processes based on CPU cores
worker_processes auto;
pid /run/nginx.pid;
# Include configurations for dynamic modules if needed
# include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Enabling Gzip compression can improve performance
# gzip on;
# This is the key to modularity!
# Nginx will load all files ending with .conf from the specified directory.
# We'll place our project configs in a folder named wiki.lib00_project_config
include /etc/nginx/wiki.lib00_project_config/*.conf;
}
```
**Key Insight:**
* **`include /etc/nginx/wiki.lib00_project_config/*.conf;`**: This is the core of the entire modular setup. It tells Nginx to load all `.conf` files from the `wiki.lib00_project_config` directory. This way, whenever you need to add, modify, or delete a project, you only need to work with its corresponding file without touching the main configuration.
---
## Step 2: Create Individual Configuration Files for Projects
Now, let's create a separate configuration file for `projectA`.
Create the file `/etc/nginx/wiki.lib00_project_config/projectA.conf`:
```nginx
server {
# Listen on the port we specified in the requirements: 55101
listen 55101;
# Bind the project's subdomain
server_name projectA.wiki.lib00.com;
# Set the website's root directory
root /web_root/lib00_projects/projectA;
# Set the default index files
index index.html index.htm;
location / {
# Try to find a file in order: $uri (file) -> $uri/ (directory) -> return 404
# This is crucial for handling routing in Single Page Applications (SPAs)
try_files $uri $uri/ =404;
}
# You can add extra configurations for specific projects, like logs, access control, etc.
access_log /var/log/nginx/projectA-access.log;
error_log /var/log/nginx/projectA-error.log;
}
```
If you have another project, `projectB`, simply copy this file to create `projectB.conf` and change the `projectA`-related names and paths to `projectB`. This approach dramatically improves the maintainability of your configuration.
---
## Step 3: Configure IP-Based Access for Development
During the development phase, subdomains might not be resolved yet. We often need to access projects using the server's IP address for testing. We can create a dedicated `server` block for this purpose.
In the same `/etc/nginx/wiki.lib00_project_config/` directory, create a test configuration file, for example, `dev-test.conf`:
```nginx
server {
# Listen on the same 55101 port
listen 55101;
# Key point: Use the server's IP address as the server_name
# When a request comes to http://192.168.1.2:55101, Nginx will match this server block
server_name 192.168.1.2;
# Point the request to projectA's root directory
root /web_root/lib00_projects/projectA;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# It's a good practice to use separate log files for the test environment
access_log /var/log/nginx/dev-test-access.log;
error_log /var/log/nginx/dev-test-error.log;
}
```
**How It Works:**
When a user accesses `http://192.168.1.2:55101`, Nginx checks the `Host` request header. Even if the browser doesn't send a `Host` header or sends the IP address, by explicitly listing the IP in `server_name`, Nginx can accurately route these requests to the `projectA` codebase.
---
## Conclusion
By adopting a modular approach using the `include` directive to separate configuration files, we have successfully built a clean, scalable, and easy-to-maintain Nginx configuration structure for a multi-project environment. This practical experience from **wiki.lib00.com** not only makes daily management more efficient but also simplifies troubleshooting. Whether for production or development testing, this structure offers significant convenience.
Related Contents
Resolving Nginx Permission Denied (13) Errors for WebP Images Generated by PHP Imagick
Duration: 00:00 | DP | 2026-07-05 21:17:00Fixing Nginx 500 Error: Internal Redirection Cycle (SPA vs PHP Config)
Duration: 00:00 | DP | 2026-07-02 21:45:50Ultimate Guide to Fixing Nginx [warn] conflicting server name Warning
Duration: 00:00 | DP | 2026-07-21 09:02:28Nginx Reverse Proxy Guide: Elegantly Routing Specific Subdirectories to Docker Containers
Duration: 00:00 | DP | 2026-07-26 21:31:06How to Fix Nginx Resource Domain CORS and 403 Forbidden Errors
Duration: 00:00 | DP | 2026-07-31 09:54:32How Can a Docker Container Access the Mac Host? The Ultimate Guide to Connecting to Nginx
Duration: 00:00 | DP | 2025-12-08 23:57:30Nginx vs. Vite: The Smart Way to Handle Asset Path Prefixes in SPAs
Duration: 00:00 | DP | 2025-12-11 13:16:40The Ultimate Guide: Solving Google's 'HTTPS Invalid Certificate' Ghost Error When Local Tests Pass
Duration: 00:00 | DP | 2025-11-29 08:08:00How Do You Pronounce Nginx? The Official Guide to Saying It Right: 'engine x'
Duration: 00:00 | DP | 2025-11-30 08:08:00Files Mysteriously Missing in PHPStorm? Check Your Project View First!
Duration: 00:00 | DP | 2026-01-15 08:16:46The Ultimate Nginx Guide: How to Elegantly Redirect Multi-Domain HTTP/HTTPS Traffic to a Single Subdomain
Duration: 00:00 | DP | 2025-11-24 20:38:27Composer Script Not Running? Unveiling the `post-install-cmd` Trap and the Ultimate Solution
Duration: 00:00 | DP | 2025-12-23 07:20:50The Ultimate Vue SPA SEO Guide: Perfect Indexing with Nginx + Static Generation
Duration: 00:00 | DP | 2025-11-28 18:25:38Can robots.txt Stop Bad Bots? Think Again! Here's the Ultimate Guide to Web Scraping Protection
Duration: 00:00 | DP | 2025-11-09 08:15:00Multilingual SEO Showdown: URL Parameters vs. Subdomains vs. Subdirectories—Which is Best?
Duration: 00:00 | DP | 2025-11-12 11:51:00From <script> Chaos to ES6 Clarity: Is Migrating to Modules Worth The Effort?
Duration: 00:00 | DP | 2025-11-11 08:16:46Nginx Redirect Trap: How to Fix Incorrectly Encoded Ampersands ('&') in URLs?
Duration: 00:00 | DP | 2025-12-31 11:34:10How to Add Port Mappings to a Running Docker Container: 3 Proven Methods
Duration: 00:00 | DP | 2026-02-05 10:16:12Recommended
Solved: MySQL Error 1054 - Unknown Column in Field List
00:00 | 4Encountering the MySQL error 'SQLSTATE[42S22]: Col...
Composer Script Not Running? Unveiling the `post-install-cmd` Trap and the Ultimate Solution
00:00 | 114Have you ever run `composer install` only to find ...
Why Encode Hashes with Base64/Base16 After MD5? A Deep Dive into Hashing vs. Encoding
00:00 | 179Many developers are familiar with hashing algorith...
From Guzzle to Native cURL: A Masterclass in Refactoring a PHP Translator Component
00:00 | 105Learn how to replace Guzzle with native PHP cURL f...