Operations 12 min read

Global Nginx Reverse Proxy: Unified Multi-Project Server Architecture with Docker

This article presents a server architecture using a single global Nginx container as the unified public entry point for multiple projects, detailing Docker Compose setup, public network configuration, SSL certificate management, and operational practices for maintainable multi-project deployments.

Code of Duty
Code of Duty
Code of Duty
Global Nginx Reverse Proxy: Unified Multi-Project Server Architecture with Docker

This is the 23rd article in the "Deploy Yourself to the Internet" series. Previous articles discussed whether Nginx should run on the host or in Docker (article 21) and why not to run a separate Nginx container per project (article 22). The author runs multiple projects — blog, personal site, portfolio, tools, experiments — on a single personal server and needs a manageable architecture.

Problems Solved

Public Entry Must Be Centralized

A server can host multiple projects, but external access should be concentrated. Users should reach blogs or sites via standard ports 80 and 443 without remembering port numbers.

Certificates Must Not Be Scattered

If HTTPS certificates live inside each project, renewal, mounting, reload, and backup become cumbersome.

Databases and Internal Services Must Not Be Exposed

Internal services stay in the internal network, proxied by Nginx; databases are only accessible to their own project.

Multi-Project Must Be Extensible

Today a blog, tomorrow a site, later more tools. Adding a project should follow a fixed onboarding pattern, not a redesign each time.

Migration Must Be Clear

When moving servers, the locations of Nginx config, certificates, project code, and data must be obvious.

These combine into one principle: external entry centralized, internal projects independent .

Overall Architecture Design

High-level flow:

Public User
 -> Domain DNS
 -> Server 80/443
 -> Global Nginx Container
 -> Different Project Services

Concrete domain routing examples:

blog.aicultiv.com -> Global Nginx -> blog-app container
www.aicultiv.com  -> Global Nginx -> site-app container or static directory
api.aicultiv.com  -> Global Nginx -> api-service container

Databases are not public: blog-app container -> blog-db container Each project manages its own application, database, cache, and background jobs via its own Docker Compose. The global Nginx handles only public ingress.

Server Directory Layout

For long-term maintainability, the author plans:

/opt/coduty/
 nginx/
   compose.yml
   conf.d/
   certs/
   logs/
 projects/
   blog/
     compose.yml
     .env
     data/
   site/
     compose.yml
     .env
     dist/
 backups/
 scripts/

Core ideas: global Nginx gets its own directory; each project gets its own; config, certs, logs, data are separated; backup and script locations are fixed. This structure directly affects future troubleshooting and migration.

Global Nginx Docker Compose

services:
  nginx:
    image: nginx:alpine
    container_name: global-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs:ro
      - ./logs:/var/log/nginx
    networks:
      - public

networks:
  public:
    external: true

Key points:

Only global Nginx binds public 80 and 443.

Other projects avoid direct public port mapping.

Nginx config mounted into container for easy editing, backup, migration.

Certificates also mounted.

Nginx joins an external public network; proxied project services also join this network.

Public Network

To proxy to different project containers, global Nginx must reach them. Separate Compose projects cannot communicate by default, so create a shared network: docker network create coduty-public Global Nginx joins this network. Application containers that need to be proxied also join it. Internal project services keep their own default network. Databases typically do not join the public network; they are only accessible to the project's own app.

Project service Compose snippet:

services:
  app:
    networks:
      - default
      - coduty-public

networks:
  coduty-public:
    external: true

This separates external ingress from internal data services.

Domain Routing in Global Nginx

Domain matching lives in global Nginx server blocks. Example for blog (HTTP only; HTTPS adds 443, cert paths, and redirect):

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

    location / {
        proxy_pass http://blog-app:8000;
        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;
    }
}

Core idea: domain matching in global Nginx, project services only expose internal ports, Nginx forwards based on domain .

New Project Onboarding Workflow

Project service joins the public network.

Confirm service name and port.

Add a server block in global Nginx conf.d.

Check Nginx syntax.

Reload Nginx.

Configure DNS and HTTPS.

SSL Certificate Management

Certificates centralized under global Nginx directory:

/opt/coduty/nginx/certs/
  example.com/
    fullchain.pem
    privkey.pem

Nginx configs reference these paths. Application, renewal, and reload all happen in one place. Wildcard certificates (e.g., *.example.com) can serve multiple subdomains; the author notes wildcard certs involve DNS validation and private key protection, to be handled separately.

Summary

Global Nginx as unified entry does not mean projects are mixed. Each project remains independent with its own compose.yml, .env, app container, database container, volumes, init scripts, and backup directory. The site project can have its own build process, static files, and deploy container.

Global Nginx cares about only one thing: which internal service a domain should forward to. It does not manage project databases or business logic.

Benefits

Clear public entry: only global Nginx occupies 80 and 443.

Fewer exposed ports: internal services not directly public.

Centralized certificate management.

Fixed onboarding process for new projects.

Layered troubleshooting: DNS → security group → firewall → global Nginx → project container → app logs.

Migration-friendly: Nginx config, certs, project directories have fixed locations.

Risks and Mitigations

Global Nginx becomes a critical node; config error can affect multiple projects.

Strict operational discipline: backup before changes; check syntax after changes; reload only after syntax passes.

One config file per domain; avoid stuffing all rules into a single large file.

Logs must be queryable.

Containerized Nginx syntax check and reload:

docker exec global-nginx nginx -t
docker exec global-nginx nginx -s reload

Suggested config file layout:

conf.d/
  blog.aicultiv.com.conf
  www.aicultiv.com.conf
  api.aicultiv.com.conf

Final architecture in bullets:

Public traffic enters only global Nginx on 80 and 443.

Nginx routes by domain to different projects.

Projects stay independent, managed by their own Docker Compose (app, DB, volumes).

Databases not exposed publicly.

Certificates managed centrally.

New projects onboard via uniform rules.

The author emphasizes this suits a single-machine, multi-project, solo-maintainer scenario. The next article will cover hands-on implementation: global Nginx container, public network, config files, and startup verification.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerDevOpsNginxReverse ProxyServer ArchitectureDocker ComposeSSL CertificatesMulti-Project Deployment
Code of Duty
Written by

Code of Duty

"Code of Duty" — Every line of code has its own mission. We avoid shortcuts and quick fixes, focusing on authentic coding reflections and the joys and challenges of technical growth. The journey of learning matters more than any destination. Join us as we humbly forge ahead in the world of code.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.