# Path-Based Routing with Reverse Proxy: Serving Multiple Websites from One Domain

Hey developers 👋

At some point in our development journey, we’ve all played around with domains, subdomains, routes, reverse proxies, and enough DNS records to make us question our life choices.

But here’s an interesting problem:

**What if you want a completely different website to appear at a specific path of your existing domain?**

Let's say I have a pastry business called **My Pastries** 🍰.

My main website lives at:

```text
mypastries.com
```

It is built with Next.js because I want server-side rendering, good performance, solid SEO, and enough flexibility to customize pretty much everything.

Everything is going great. Then I decide:

> "We should probably start a blog."

Not just because everyone keeps saying *content is king*, but because I want to publish useful content, build topical authority, attract organic traffic, and generally give the website more reasons to exist than just "here are our cakes."

Now, I could build the entire blogging system myself.

Or I could save myself the headache and use WordPress or another CMS that already solved most of these problems years ago.

So I deploy my blog separately.

My infrastructure now looks something like this:

```text
172.1.0.1 → Main Next.js website
172.1.0.2 → WordPress/CMS blog
```

But now comes the interesting part. I don't want users to visit:

```text
blog.mypastries.com
```

I want them to visit:

```text
mypastries.com/blog
```

And if they open an article:

```text
mypastries.com/blog/how-to-make-the-perfect-croissant
```

I want the **second server** to handle that request.

But I still want the browser to show:

```text
mypastries.com/blog/how-to-make-the-perfect-croissant
```

No redirect.

No separate domain.

No making the user care where my blog is actually hosted.

So how do I make two completely different servers behave like one website?

This is where **reverse proxies and path-based routing** come in.

* * *

## 1\. What Is a Reverse Proxy?

Let's start with the simplest mental model.

A reverse proxy is a server that sits between the client and one or more backend servers.

Instead of the browser directly talking to my application server, it talks to the reverse proxy.

The reverse proxy then decides where that request actually needs to go.

In our example:

```text
                     ┌──────────────────┐
                     │                  │
User ───────────────►│  Nginx           │
                     │  Reverse Proxy   │
                     │                  │
                     └────────┬─────────┘
                              │
                       ┌──────┴──────┐
                       │             │
                       ▼             ▼
                  172.1.0.1      172.1.0.2
                  Main Website     Blog
```

The important thing here is that the user doesn't need to know about `172.1.0.1` or `172.1.0.2`.

As far as the user is concerned, they're talking to:

```text
mypastries.com
```

Nginx is the one making the decision about which server should actually handle the request.

And this is why reverse proxies are so useful.

I can have:

```text
mypastries.com/
mypastries.com/products
mypastries.com/about
```

going to one application while:

```text
mypastries.com/blog
mypastries.com/blog/recipes
mypastries.com/blog/my-first-post
```

goes to another.

From the outside, it looks like one website.

Internally, it can be several completely independent applications.

* * *

## 2\. Reverse Proxy vs Redirect

This distinction confused me the first time I started working with proxy configurations, so it's worth making explicit.

A **redirect** tells the browser:

> "Hey, don't request this URL anymore. Go request this other URL."

For example:

```text
Browser
   │
   │ GET /blog
   ▼
Server
   │
   │ 301 Redirect
   ▼
blog.mypastries.com
```

The browser receives the redirect and makes another request.

A reverse proxy works differently.

```text
Browser
   │
   │ GET /blog
   ▼
Nginx
   │
   │ proxy request
   ▼
172.1.0.2
   │
   │ response
   ▼
Nginx
   │
   ▼
Browser
```

The browser never needs to know that Nginx forwarded the request somewhere else.

This is the important bit:

**The URL stays the same from the user's perspective.**

So when the user visits:

```text
mypastries.com/blog/my-first-post
```

the browser continues to show that URL even if Nginx actually fetched the content from:

```text
172.1.0.2
```

That's fundamentally different from an HTTP redirect.

* * *

## 3\. Path-Based Routing

Now that we understand the reverse proxy part, let's add one more piece:

**path-based routing.**

The idea is pretty straightforward.

Instead of routing traffic only based on the domain, I can tell Nginx:

> "If the request starts with `/blog`, send it to the blog server."

For example:

```text
Request                                      Destination

mypastries.com/                             → 172.1.0.1
mypastries.com/about                        → 172.1.0.1
mypastries.com/products                     → 172.1.0.1

mypastries.com/blog                         → 172.1.0.2
mypastries.com/blog/recipes                 → 172.1.0.2
mypastries.com/blog/my-first-post           → 172.1.0.2
```

This is generally called **path-based routing**.

And once you understand this pattern, you can use it for much more than blogs.

For example:

```text
example.com/       → Main website
example.com/app/   → Application
example.com/api/   → API
example.com/admin/ → Admin panel
example.com/docs/  → Documentation
```

All of these can potentially live on completely different servers or services.

* * *

## 4\. The Architecture

Before touching Nginx, let's make the architecture clear.

I'll use this setup throughout the article:

```text
                        mypastries.com
                              │
                              ▼
                    ┌─────────────────┐
                    │      Nginx      │
                    │ Reverse Proxy   │
                    └────────┬────────┘
                             │
                ┌────────────┴────────────┐
                │                         │
             / request                 /blog request
                │                         │
                ▼                         ▼
          172.1.0.1                  172.1.0.2
          Next.js App                Blog/CMS
```

The basic rule is:

```text
/       → 172.1.0.1
/blog   → 172.1.0.2
```

The domain points to the machine running Nginx.

Nginx then acts as the traffic controller.

And importantly, **DNS does not perform the** `/blog` **routing**.

DNS only helps get:

```text
mypastries.com
```

to the server where Nginx is running.

Once the HTTP request reaches Nginx, Nginx looks at the request path and decides what to do with it.

That's an important distinction.

* * *

## 5\. Configuring Nginx

Now let's actually configure it.

For the simplest version, our Nginx configuration can look like this:

```nginx
server {
    listen 80;
    server_name mypastries.com;

    location / {
        proxy_pass http://172.1.0.1;
    }

    location /blog/ {
        proxy_pass http://172.1.0.2;
    }
}
```

That's already enough to demonstrate the basic concept.

But let's break it down instead of treating Nginx configuration like ancient magic that must be copied from Stack Overflow without questioning it.

### The `server` block

```nginx
server {
    listen 80;
    server_name mypastries.com;
}
```

This tells Nginx that this configuration should handle requests for:

```text
mypastries.com
```

on port `80`.

In a real production deployment, I'd normally be dealing with HTTPS and port `443`, but I'm keeping the first example intentionally simple.

* * *

### The default route

Next:

```nginx
location / {
    proxy_pass http://172.1.0.1;
}
```

This basically says:

> "For requests matching `/`, proxy them to `172.1.0.1`."

So requests such as:

```text
mypastries.com/
mypastries.com/about
mypastries.com/products
```

will be handled by the main website.

* * *

### The blog route

Then I add:

```nginx
location /blog/ {
    proxy_pass http://172.1.0.2;
}
```

Now Nginx has a more specific route for requests starting with `/blog/`.

So:

```text
mypastries.com/blog/
mypastries.com/blog/recipes
mypastries.com/blog/my-first-post
```

are sent to:

```text
172.1.0.2
```

This is the basic mechanism we're after.

* * *

## 6\. Don't Forget the Forwarded Headers

There is another part of reverse proxy configuration that I wouldn't skip in a real deployment.

The upstream application often needs to know things about the original request:

*   What host did the user request?
    
*   What was the original client IP?
    
*   Was the original request HTTPS?
    
*   What protocol was being used?
    

Nginx can forward that information through headers.

A more realistic configuration would therefore look like:

```nginx
location /blog/ {
    proxy_pass http://172.1.0.2;

    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;
}
```

These headers are especially useful when the application generates absolute URLs, performs redirects, logs client addresses, or needs to understand whether the original request was HTTP or HTTPS.

I generally prefer setting these explicitly rather than discovering six months later that the application thinks every visitor is coming from my Nginx server.

* * *

## 7\. The `proxy_pass` Trailing Slash Trap

Now we get to one of those Nginx details that looks completely harmless until it ruins your afternoon.

Consider:

```nginx
location /blog/ {
    proxy_pass http://172.1.0.2;
}
```

versus:

```nginx
location /blog/ {
    proxy_pass http://172.1.0.2/;
}
```

That trailing `/` matters.

Nginx handles the URI differently depending on how `proxy_pass` is written.

If I have:

```text
/blog/my-first-post
```

and use:

```nginx
proxy_pass http://172.1.0.2;
```

the upstream request can retain the `/blog/` portion:

```text
172.1.0.2/blog/my-first-post
```

Whereas with:

```nginx
proxy_pass http://172.1.0.2/;
```

Nginx can replace the matching `/blog/` portion and send:

```text
172.1.0.2/my-first-post
```

That difference is extremely important.

Why?

Because I need to know what my application actually expects.

If my WordPress installation is configured to believe it lives at:

```text
/blog
```

then keeping the prefix may be what I want.

If the application is actually running at the root of the upstream server and knows nothing about `/blog`, I may need to strip the prefix.

This is one of the first things I check whenever I get a mysterious `404` after configuring a reverse proxy.

* * *

## 8\. Serving an Application Under a Subpath

Here's where things get more interesting.

Routing the request is only **half the problem**.

Suppose my blog application is perfectly accessible when I visit:

```text
http://172.1.0.2/
```

I configure Nginx and suddenly visit:

```text
https://mypastries.com/blog/
```

The HTML loads.

Great.

Then I open the browser developer tools.

And suddenly I see:

```text
GET /assets/main.js 404
GET /styles.css 404
GET /images/logo.png 404
```

And I start wondering why Nginx has personally betrayed me.

The problem is usually not the proxy itself.

The application may be generating URLs relative to the root of the domain.

For example:

```html
<script src="/assets/main.js"></script>
```

The browser interprets that as:

```text
mypastries.com/assets/main.js
```

But I actually need:

```text
mypastries.com/blog/assets/main.js
```

Now the request goes through Nginx's `/` location instead of `/blog/`.

So the main application gets a request for an asset it doesn't have.

This is why **subpath deployments need application-level awareness**.

Depending on the application, I may need to configure things such as:

*   base URL
    
*   base path
    
*   asset prefix
    
*   public URL
    
*   canonical URL
    
*   cookie path
    
*   redirect URLs
    

The exact setting depends on the application.

The reverse proxy can route traffic, but it can't magically rewrite every assumption the application makes about where it lives.

* * *

## 9\. Static Assets Can Be the First Sign Something Is Wrong

This is worth checking whenever a proxied application partially loads.

If the page looks broken, open DevTools → Network and look for:

```text
404
403
301
302
```

especially for:

```text
.js
.css
.png
.svg
.webp
fonts
API requests
```

For example, if I see:

```text
/blog/                    200
/blog/article             200
/assets/app.js            404
/assets/app.css           404
```

that's a pretty good indication that the application doesn't understand that it is being served under `/blog`.

The proxy might be doing exactly what I told it to do.

The application simply doesn't know where it lives.

* * *

## 10\. Redirects Can Also Break

Another fun one.

Suppose the application receives:

```text
/blog/login
```

and decides the user isn't authenticated.

It might respond with:

```http
Location: /login
```

The browser then goes to:

```text
mypastries.com/login
```

instead of:

```text
mypastries.com/blog/login
```

And congratulations, we're back on the main website.

This is why reverse-proxy deployments often require coordination between:

**Nginx configuration + application configuration.**

The proxy knows the external path.

The application needs to know that it is effectively living under that path.

* * *

## 11\. Cookies and Sessions

Cookies are another thing I check when putting an application behind a path.

For example, an application may set:

```http
Set-Cookie: session=abc123; Path=/
```

That cookie is available across the entire domain.

Sometimes that's perfectly fine.

Sometimes I specifically want the cookie scoped to:

```text
/blog
```

depending on the application and architecture.

Authentication can become especially interesting when the main application and the proxied application both use cookies, sessions, or authentication middleware.

So if the application works perfectly on its own domain but login suddenly behaves strangely behind `/blog`, cookies and redirects are two of the first places I'd investigate.

* * *

## 12\. WebSockets and Other Special Cases

Normal HTTP requests are usually straightforward.

But some applications also rely on:

*   WebSockets
    
*   Server-Sent Events
    
*   streaming responses
    
*   long-running connections
    

For WebSockets, for example, Nginx may need the appropriate upgrade headers:

```nginx
location /blog/ {
    proxy_pass http://172.1.0.2;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    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;
}
```

Whether you need this depends entirely on what the upstream application is doing.

I wouldn't blindly add every possible Nginx directive I find on the internet.

Understand what the application needs first.

* * *

## 13\. A More Production-Friendly Configuration

Once the basic routing works, I would normally move toward something closer to this:

```nginx
server {
    listen 80;
    server_name mypastries.com;

    location / {
        proxy_pass http://172.1.0.1;

        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;
    }

    location /blog/ {
        proxy_pass http://172.1.0.2;

        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;
    }
}
```

From here, I can add HTTPS, access/error logging, timeouts, buffering configuration, caching, rate limiting, health checks, and other production requirements depending on the application.

But I prefer getting the routing correct **before** turning the Nginx configuration into a 200-line configuration file that nobody wants to touch.

* * *

## 14\. How I Think About the Request Flow

Whenever I'm debugging a reverse proxy, I find it easier to stop looking at the Nginx configuration for a moment and trace the request.

Suppose the user requests:

```text
https://mypastries.com/blog/recipes/croissant
```

The flow is roughly:

### Step 1 — DNS

The domain resolves to the server running Nginx.

```text
mypastries.com
       ↓
Nginx Server
```

### Step 2 — Client sends HTTP request

The browser sends something like:

```http
GET /blog/recipes/croissant HTTP/1.1
Host: mypastries.com
```

### Step 3 — Nginx matches the request

Nginx sees:

```text
/blog/recipes/croissant
```

and matches it against:

```nginx
location /blog/
```

### Step 4 — Nginx proxies the request

Nginx forwards the request to:

```text
172.1.0.2
```

Depending on the `proxy_pass` configuration, the upstream URI may be:

```text
/blog/recipes/croissant
```

or:

```text
/recipes/croissant
```

### Step 5 — Upstream generates the response

The blog server processes the request and returns HTML, headers, cookies, etc.

### Step 6 — Nginx returns the response

Nginx sends the response back to the browser.

The browser still sees:

```text
https://mypastries.com/blog/recipes/croissant
```

It doesn't need to know that the content came from:

```text
172.1.0.2
```

And that's essentially the whole trick.

* * *

## 15\. Common Problems I Would Check First

When this setup doesn't work, I wouldn't immediately start randomly changing Nginx directives.

I'd go through the request systematically.

### The domain doesn't work

Check:

```text
DNS
↓
Server IP
↓
Nginx running?
↓
Port 80/443 open?
↓
Correct server_name?
```

### The main website works but `/blog` returns 404

Check:

```text
location matching
↓
proxy_pass
↓
upstream application
↓
whether the upstream expects /blog or /
```

### HTML works but CSS/JS doesn't

Check:

```text
Generated asset URLs
↓
Application base path
↓
Nginx location matching
```

### Login works but redirects to the wrong place

Check:

```text
Location headers
↓
Application base URL
↓
proxy_redirect
↓
cookie configuration
```

### The application thinks everyone has the same IP

Check:

```text
X-Real-IP
X-Forwarded-For
```

### WebSockets don't work

Check:

```text
HTTP/1.1
Upgrade
Connection
```

This approach saves a lot of time because you're debugging the **request flow**, not just throwing configuration directives at the problem.

* * *

## 16\. Path-Based Routing vs Subdomains

At this point, it's worth asking:

**Why not just use** `blog.mypastries.com`**?**

Honestly, sometimes that's the better solution.

Subdomains are simpler when the application is completely independent and doesn't need to pretend that it lives inside the main website.

For example:

```text
www.mypastries.com
blog.mypastries.com
admin.mypastries.com
api.mypastries.com
```

can be a perfectly sensible architecture.

Path-based routing becomes particularly interesting when I want the external experience to look like one unified application:

```text
mypastries.com/
mypastries.com/blog/
mypastries.com/docs/
mypastries.com/app/
```

It can also be useful when I want different technologies, deployments, or teams to own different sections of the same public-facing domain.

The important thing is not to treat one approach as universally better.

**Choose the URL architecture based on how the applications actually need to work.**

* * *

## 17\. The Bigger Picture

The pastry blog was just a convenient example.

The same pattern can be used to put completely different applications behind a single domain:

```text
                        example.com
                             │
                             ▼
                           Nginx
                             │
       ┌─────────────┬───────┼───────────┬─────────────┐
       │             │       │           │             │
       ▼             ▼       ▼           ▼             ▼
      /              /app    /api        /admin        /docs
       │              │       │           │             │
       ▼              ▼       ▼           ▼             ▼
   Website         App     Backend     Admin Panel    Docs
```

The applications don't even need to use the same technology.

I could have:

```text
/       → Next.js
/blog   → WordPress
/api    → Node.js
/admin  → React
/docs   → another static site
```

And Nginx can sit in front of all of them.

That's the real value of path-based reverse proxying.

I'm not necessarily creating one giant application.

I'm creating **one public entry point for multiple independent applications**.

* * *

## 18\. Why Not Use an API Gateway or Cloud Service?

At this point, you might be thinking:

> "Why configure Nginx myself? Can't I just use an API Gateway or some managed cloud service?"

Of course you can. In fact, depending on the scale and architecture, you probably should.

Services like AWS Application Load Balancer, API Gateway, CloudFront, or similar managed solutions can handle routing, TLS, health checks, scaling, monitoring, and high availability without you having to maintain the proxy server yourself.

But for our use case, we're simply doing:

```text
mypastries.com/
      ↓
   Server A

mypastries.com/blog/
      ↓
   Server B
```

If I already have a server running Nginx, adding a managed service just for this can introduce unnecessary infrastructure and cost.

So my general rule is:

**Use Nginx when the routing is simple and you want direct control. Use a managed service when you need scalability, high availability, cloud integrations, or don't want to maintain the infrastructure yourself.**

And one small clarification: an **API Gateway isn't necessarily the natural choice just because we're routing requests**. API gateways are primarily designed around API management, while a reverse proxy or load balancer is often a better fit for routing websites and applications.

The goal isn't to use the fanciest infrastructure available.

**It's to use the simplest infrastructure that solves the problem reliably.**

## Conclusion

Reverse proxying looks complicated when you first encounter a giant Nginx configuration with twenty `location` blocks and enough directives to make you reconsider your career.

But the fundamental idea is actually pretty simple:

```text
Client
  ↓
Nginx
  ↓
Choose upstream based on request
  ↓
Backend application
```

With path-based routing, that decision can be based on the URL:

```text
/       → Server A
/blog   → Server B
/api    → Server C
/admin  → Server D
```

The important part is remembering that **routing the request and making the application work under that route are two different problems**.

Nginx can happily forward `/blog/my-post` to your blog server.

It can't automatically fix an application that still thinks it lives at `/`.

That's where most of the interesting problems begin—asset paths, redirects, cookies, WebSockets, canonical URLs, and application-specific base paths.

Once you understand that distinction, reverse proxy configuration stops feeling like mysterious DevOps wizardry and starts looking like what it really is:

**a traffic-routing problem sitting between your users and your applications.**

* * *

I hope you found this helpful! If you have any feedback or suggestions for improvement, please feel free to reach out. I'm always looking to learn and improve, and your input is invaluable. If you have read it till now, thank you so much for reading!, please leave your comments if any ✌️

Don't forget to bookmark this blog for the future 📌

Connect with the author:

*   [**LinkedIn**](https://www.linkedin.com/in/sanchitbajaj02)
    
*   [**GitHub**](https://github.com/sanchitbajaj02)
    
*   [**Twitter**](https://twitter.com/solitrix02)
