I have Next.js 13 (as frontend) and Nestjs (as API) both Dockerized and reverse proxied using nginx-proxy
image, here is my docker-compose.yml
:
api:
image: gidgud/my:api
container_name: api
ports:
- '6900:6999'
networks:
- mynetwork
environment:
- VIRTUAL_HOST=api.mywebsite.com
- VIRTUAL_PORT=6999
web:
image: gidgud/my:web
container_name: web
expose:
- '3000'
networks:
- mynetwork
depends_on:
- api
environment:
- VIRTUAL_HOST=www.mywebsite.com,mywebsite.com
- VIRTUAL_PORT=3000
nginx-proxy:
image: 'nginxproxy/nginx-proxy:latest'
container_name: 'nginx-proxy'
volumes:
- 'html:/usr/share/nginx/html'
- 'dhparam:/etc/nginx/dhparam'
- 'vhost:/etc/nginx/vhost.d'
- 'certs:/etc/nginx/certs'
- 'conf:/etc/nginx/conf.d'
- '/var/run/docker.sock:/tmp/docker.sock:ro'
- './custom_proxy.conf:/etc/nginx/conf.d/custom_proxy.conf'
depends_on:
- web
- api
networks:
- mynetwork
ports:
- '80:80'
- '443:443'
networks:
mynetwork:
name: my_network
external: true
My web
request to api
using the VM IP Address with the api
's exposed port (I'm trying to point it to api.mywebsite.com
instead, but Next.js giving TIMEOUT
error when doing server request to it, that's why for now I point it to the VM IP Address).
Need to know: my web
is not directly requesting to api
's IP Address but it goes through Next.js rewrites
first. So I 'wrap' the api
's IP Address with Next.js rewrites
feature.
Okay above is my website setup and here comes the main problem.
My api
cannot receive the real client IP Address that comes from web
request. Instead, it always receives nginx-proxy
container's IP Address. Moreover, when web
doing server request to the api
(Next.js server component), the api
receives the Docker network default gateway IP Address instead.
Beside that, I've tried to deploy a different website on Vercel without Docker and try to send request to the api
(api.mywebsite.com
) and my api
can correctly receive the real client IP Address. According to this findings, I suspect the problem is either from the Docker or Next.js setup.
Question: How do I setup Next.js/Docker so that when it request to api
it will also send at least the X-Forwarded-For
header? (for real client IP Address)
Thank you and for your answers ^^
After surfing and testing many times, here I come up with this solution.
In order to receive end user real IP, need to activate trust proxy
setting like this:
// main.ts
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// If your Next.js (client) on the same server/IP with the Nest.js use this
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
// If your Next.js (client) on different server/IP from the Nest.js use this
// app.set('trust proxy', true);
...
}
Got the Reference when trying to rate limit my app: https://docs.nestjs.com/security/rate-limiting#proxies
rewrites
by default doesn't forward end user headers, so I need to append them manually like this:// middleware.ts
export const config = {
matcher: '/my-api/:path*',
};
export function middleware(req: NextRequest) {
const baseUrl = process.env.API_URL;
const requestHeaders = new Headers(req.headers);
const url = req.nextUrl.clone();
url.protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
url.pathname = url.pathname.replace(/^\/my-api/, '');
url.href = baseUrl + url.pathname + '?' + url.searchParams;
return NextResponse.rewrite(url, {
request: {
headers: requestHeaders,
},
});
}
Other options beside using Next.js rewrites
would be using http-proxy-middleware
package to create custom proxy to api.
fetch
by default also doesn't forward/include end user headers. So I need to append them in every fetch
request like this:import { headers } from "next/headers";
...
const response = await fetch(`${baseUrl}/api${path}`, {
...options,
headers: Object.fromEntries(headers())
});
Reference about this server request headers: https://github.com/vercel/next.js/issues/47793
As reverse proxy, to make sure it forwards end user real IP to your web app, need to add these lines in Nginx config:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
In my case, I use nginx-proxy
docker image that already have above config by default so I don't need to add extra configuration to my Nginx container.
Hopefully this helps! And I still open for other solution, suggestion, and answer, Thanks!