Preserve and Forward X-Forwarded-For Header in Outgoing Requests to *arr #9231

Closed
opened 2026-02-20 00:11:45 -05:00 by deekerman · 15 comments
Owner

Originally created by @Patrick010 on GitHub (Jul 22, 2025).

Is there an existing issue for this?

  • I have searched the existing open and closed issues

In many proxied environments (e.g. with Caddy or Nginx), Radarr receives the client’s public IP via the X-Forwarded-For header from a trusted reverse proxy or other application, like Jellyseer. This works as expected. Radarr logs and uses the client IP.

However, when Radarr forwards that request downstream to other applications like Jellyfin, it performs a new outbound HTTP request without preserving or forwarding the original X-Forwarded-For header. As a result, Jellyfin only sees the IP address of the Radarr container or internal host.

This breaks IP attribution across the chain; logs, rate limiting, and audit trails lose the original client context. This is especially relevant in setups where multiple services (e.g. Jellyseerr -> Radarr -> Jellyfin) interact and decisions or logs depend on accurate IP tracing.

Why not just add another proxy?

Yes, technically a reverse proxy could be inserted in front of Radarr to re-inject the missing headers, but doing so adds unnecessary complexity. It introduces more containers, more moving parts, and more points of failure, just to simulate a behavior that could and should be handled at the application level.

Radarr already has access to the original request and headers. It is in the perfect position to pass that information downstream without relying on an external proxy to patch the gap.

This isn’t about offloading work to a proxy, it’s about maintaining context as a responsible link in a multi-service chain.

Proposal

Radarr should:

  1. When receiving a request from a client or reverse proxy, read and store the X-Forwarded-For header, if present.
  2. When making API requests to the next application or services, include the same X-Forwarded-For header to preserve client identity.
  3. Optionally, append its own IP to the chain, in standard proxy style.

Example how the header could be assembled:

// Forward X-Forwarded-For header downstream
  const response = await fetch(`http://jellyfin.internal${apiPath}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${jellyfinApiKey}`,
      'Content-Type': 'application/json',
      'X-Forwarded-For': forwardedForHeader,
    },

Why?

  1. Accurate IP logging across the media stack
  2. Preserves the original client identity across service boundaries
  3. Avoids unnecessary proxies just to inject headers
  4. Aligns with common web architecture best practices

Suggested config flag

To keep it optional and safe for all environments, this could be gated behind a config option like:

preserve_forwarded_ip: true

Should this for whatever reason already be possible, then disregard this request.

Describe the solution you'd like

Proposal

Radarr should:

  1. When receiving a request from a client or reverse proxy, read and store the X-Forwarded-For header, if present.
  2. When making API requests to the next application or services, include the same X-Forwarded-For header to preserve client identity.
  3. Optionally, append its own IP to the chain, in standard proxy style.

Example how the header could be assembled:

// Forward X-Forwarded-For header downstream
  const response = await fetch(`http://jellyfin.internal${apiPath}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${jellyfinApiKey}`,
      'Content-Type': 'application/json',
      'X-Forwarded-For': forwardedForHeader,
    },

Describe alternatives you've considered

A reverse proxy could be inserted in front of Radarr to re-inject the missing headers, but doing so adds unnecessary complexity. It introduces more containers, more moving parts, and more points of failure, just to simulate a behavior that could and should be handled at the application level.

Anything else?

.

Originally created by @Patrick010 on GitHub (Jul 22, 2025). ### Is there an existing issue for this? - [x] I have searched the existing open and closed issues ### Is your feature request related to a problem? Please describe In many proxied environments (e.g. with Caddy or Nginx), Radarr receives the client’s public IP via the X-Forwarded-For header from a trusted reverse proxy or other application, like Jellyseer. This works as expected. Radarr logs and uses the client IP. However, when Radarr forwards that request downstream to other applications like Jellyfin, it performs a new outbound HTTP request without preserving or forwarding the original X-Forwarded-For header. As a result, Jellyfin only sees the IP address of the Radarr container or internal host. This breaks IP attribution across the chain; logs, rate limiting, and audit trails lose the original client context. This is especially relevant in setups where multiple services (e.g. Jellyseerr -> Radarr -> Jellyfin) interact and decisions or logs depend on accurate IP tracing. ### Why not just add another proxy? Yes, technically a reverse proxy could be inserted in front of Radarr to re-inject the missing headers, but doing so adds unnecessary complexity. It introduces more containers, more moving parts, and more points of failure, just to simulate a behavior that could and should be handled at the application level. Radarr already has access to the original request and headers. It is in the perfect position to pass that information downstream without relying on an external proxy to patch the gap. This isn’t about offloading work to a proxy, it’s about maintaining context as a responsible link in a multi-service chain. ### Proposal Radarr should: 1. When receiving a request from a client or reverse proxy, read and store the X-Forwarded-For header, if present. 2. When making API requests to the next application or services, include the same X-Forwarded-For header to preserve client identity. 3. Optionally, append its own IP to the chain, in standard proxy style. ### Example how the header could be assembled: ``` // Forward X-Forwarded-For header downstream const response = await fetch(`http://jellyfin.internal${apiPath}`, { method: 'POST', headers: { 'Authorization': `Bearer ${jellyfinApiKey}`, 'Content-Type': 'application/json', 'X-Forwarded-For': forwardedForHeader, }, ``` ### Why? 1. Accurate IP logging across the media stack 2. Preserves the original client identity across service boundaries 3. Avoids unnecessary proxies just to inject headers 4. Aligns with common web architecture best practices ### Suggested config flag To keep it optional and safe for all environments, this could be gated behind a config option like: `preserve_forwarded_ip: true` Should this for whatever reason already be possible, then disregard this request. ### Describe the solution you'd like ### Proposal Radarr should: 1. When receiving a request from a client or reverse proxy, read and store the X-Forwarded-For header, if present. 2. When making API requests to the next application or services, include the same X-Forwarded-For header to preserve client identity. 3. Optionally, append its own IP to the chain, in standard proxy style. ### Example how the header could be assembled: ``` // Forward X-Forwarded-For header downstream const response = await fetch(`http://jellyfin.internal${apiPath}`, { method: 'POST', headers: { 'Authorization': `Bearer ${jellyfinApiKey}`, 'Content-Type': 'application/json', 'X-Forwarded-For': forwardedForHeader, }, ``` ### Describe alternatives you've considered A reverse proxy could be inserted in front of Radarr to re-inject the missing headers, but doing so adds unnecessary complexity. It introduces more containers, more moving parts, and more points of failure, just to simulate a behavior that could and should be handled at the application level. ### Anything else? .
Author
Owner

@bakerboy448 commented on GitHub (Jul 22, 2025):

What is the usecase for radarr being used as a command and control client for Jellyfin? What request is made directly to radarr that then results in an immediate and direct call to Jellyfin with no intermediate steps?

@bakerboy448 commented on GitHub (Jul 22, 2025): What is the usecase for radarr being used as a command and control client for Jellyfin? What request is made directly to radarr that then results in an immediate and direct call to Jellyfin with no intermediate steps?
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

The usecase is in 'Why'; logging of the correct source IP in stead of the Radarr local IP.
Radarr will get the public IP from the system calling it, being either a proxy, or possibly Jellyfin.
It has nothing to do with being a command and control client, it is just sending the requester's IP, like it should. There is no mayor overhaul or changes needed, only determining the incoming X-Forwarded-For IP and adding that to the header of the Jellyfin (or other) API call.

@Patrick010 commented on GitHub (Jul 22, 2025): The usecase is in 'Why'; logging of the correct source IP in stead of the Radarr local IP. Radarr will get the public IP from the system calling it, being either a proxy, or possibly Jellyfin. It has nothing to do with being a command and control client, it is just sending the requester's IP, like it should. There is no mayor overhaul or changes needed, only determining the incoming X-Forwarded-For IP and adding that to the header of the Jellyfin (or other) API call.
Author
Owner

@bakerboy448 commented on GitHub (Jul 22, 2025):

No plans to arbitrarily pass the forwarded header to subsequent commands.

No functionality exists where radarr is getting by a direct api call that is immediately passed through to another application.

Radarr is not a command and control client for sending and monitoring instructions to your media server. It is a media library manager.

Various headers are already handled github.com/Radarr/Radarr@71e1003358

@bakerboy448 commented on GitHub (Jul 22, 2025): No plans to arbitrarily pass the forwarded header to subsequent commands. No functionality exists where radarr is getting by a direct api call that is immediately passed through to another application. Radarr is not a command and control client for sending and monitoring instructions to your media server. It is a media library manager. Various headers are already handled https://github.com/Radarr/Radarr/commit/71e1003358ddbbe5ecdedc4ca3d6d0781a5188c9
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

Thanks for your flexibility and for thinking along. I honestly don’t get why they can’t just tweak a handful of lines of code to forward the correct X-Forwarded-For IP to downstream systems. Classic OSS “not invented here” mentality.

@Patrick010 commented on GitHub (Jul 22, 2025): Thanks for your flexibility and for thinking along. I honestly don’t get why they can’t just tweak a handful of lines of code to forward the correct X-Forwarded-For IP to downstream systems. Classic OSS “not invented here” mentality.
Author
Owner

@mynameisbogdan commented on GitHub (Jul 22, 2025):

Because Radarr is not a proxy nor a load balancer to make use of that header, thus what you desire it's not the intended purpose for a client to pass the header along.

Considering the automation, many requests aren't even executed from a "parent request".

Let's not break how apps are working just because.

@mynameisbogdan commented on GitHub (Jul 22, 2025): Because Radarr is not a proxy nor a load balancer to make use of that header, thus what you desire it's not the intended purpose for a client to pass the header along. Considering the automation, many requests aren't even executed from a "parent request". Let's not break how apps are working just because.
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

My requests improves security by forwarding and logging the source IP.
Again, why the resistance agains putting the proper source IP in the API request? It doesnt make it a LB or Proxy. Its the correct way.
I dont ask for big changes, only a header adjustment in API calls.

@Patrick010 commented on GitHub (Jul 22, 2025): My requests improves security by forwarding and logging the source IP. Again, why the resistance agains putting the proper source IP in the API request? It doesnt make it a LB or Proxy. Its the correct way. I dont ask for big changes, only a header adjustment in API calls.
Author
Owner

@RobinDadswell commented on GitHub (Jul 22, 2025):

Hi Patrick, I think you may be missing what's been said here.

Most, if not all, of the requests that Radarr makes to other services such as Jellyfin are not done from the perspective of the user triggering something, but are done via the automation. E.g. when an import happens, 99% of the time this is not done off the back of a user saying "import this file".

Due to this, we are not going to be adding the x-forwarded-for header to downstreams systems as the traffic doesn't originate downstream but in fact simply originates from the Radarr service.

@RobinDadswell commented on GitHub (Jul 22, 2025): Hi Patrick, I think you may be missing what's been said here. Most, if not all, of the requests that Radarr makes to other services such as Jellyfin are not done from the perspective of the user triggering something, but are done via the automation. E.g. when an import happens, 99% of the time this is not done off the back of a user saying "import this file". Due to this, we are not going to be adding the x-forwarded-for header to downstreams systems as the traffic doesn't originate downstream but in fact simply originates from the Radarr service.
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

Except when it is triggered by something like Jellyseer. I see requests in my Jellyfin log originating from Radarr while i know that these resquests come from Jellyseer.
And even if it IS originating from Radarr itself, then still it should should fwd the IP of the originating system, not its own. Basic security origin tracebility.

@Patrick010 commented on GitHub (Jul 22, 2025): Except when it is triggered by something like Jellyseer. I see requests in my Jellyfin log originating from Radarr while i know that these resquests come from Jellyseer. And even if it IS originating from Radarr itself, then still it should should fwd the IP of the originating system, not its own. Basic security origin tracebility.
Author
Owner

@RobinDadswell commented on GitHub (Jul 22, 2025):

Except when it is triggered by something like Jellyseer. I see requests in my Jellyfin log originating from Radarr while i know that these resquests come from Jellyseer. And even if it IS originating from Radarr itself, then still it should should fwd the IP of the originating system, not its own. Basic security origin tracebility.

Jellyseer is still not the one doing the import, it is purely doing the request in the first place. Radarr then takes over. If you want Jellyseer to pass the IP to radarr then that is something to talk to the Jellyseer team about not us.

As I said, once Radarr gets hold of "monitor this" everything else is handled by Radarr itself and not the originating system in anyway

@RobinDadswell commented on GitHub (Jul 22, 2025): > Except when it is triggered by something like Jellyseer. I see requests in my Jellyfin log originating from Radarr while i know that these resquests come from Jellyseer. And even if it IS originating from Radarr itself, then still it should should fwd the IP of the originating system, not its own. Basic security origin tracebility. Jellyseer is still not the one doing the import, it is purely doing the request in the first place. Radarr then takes over. If you want Jellyseer to pass the IP to radarr then that is something to talk to the Jellyseer team about not us. As I said, once Radarr gets hold of "monitor this" everything else is handled by Radarr itself and not the originating system in anyway
Author
Owner

@mynameisbogdan commented on GitHub (Jul 22, 2025):

Basic security origin tracebility.

Where did you read this?

@mynameisbogdan commented on GitHub (Jul 22, 2025): > Basic security origin tracebility. Where did you read this?
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

If you dont understand, just say so, nothing wrong with that.
Radarr, or any system for that matter, should report the originating IP, not its own. Has nothing to do with what system is making the call to Radarr. Radarr will communicate with other systems like Jellyfin. The API call to Jellyfin (in this case) should contain the X-Forwarded-For IP where the request originally came from. All that has to be done is look at the originating IP, which it already does, and put that in the API call to the next system.
If you dont care about security enhancements you can ignore it. Others do care, so again, why the resistance?

@Patrick010 commented on GitHub (Jul 22, 2025): If you dont understand, just say so, nothing wrong with that. Radarr, or any system for that matter, should report the originating IP, not its own. Has nothing to do with what system is making the call to Radarr. Radarr will communicate with other systems like Jellyfin. The API call to Jellyfin (in this case) should contain the X-Forwarded-For IP where the request originally came from. All that has to be done is look at the originating IP, which it already does, and put that in the API call to the next system. If you dont care about security enhancements you can ignore it. Others do care, so again, why the resistance?
Author
Owner

@RobinDadswell commented on GitHub (Jul 22, 2025):

This is going to be the final thing on the topic. The originating system for 99.9999999% of things that Radarr does is Radarr itself. Just because a request has been put into the system in the first place by a 3rd party tool does not mean that any future updates mean that it's the origin of that specific API call.

@RobinDadswell commented on GitHub (Jul 22, 2025): This is going to be the final thing on the topic. The originating system for 99.9999999% of things that Radarr does is Radarr itself. Just because a request has been put into the system in the first place by a 3rd party tool does not mean that any future updates mean that it's the origin of that specific API call.
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

Basic security origin tracebility.

Where did you read this?

ISO/IEC 27002:2022 – Clause 5.12 (Event Logging)

It requires logs to:

Record user activities, exceptions, and information security events.
Include sufficient information to trace actions to individuals.
This explicitly includes session source identifiers like IP addresses, timestamps, and user IDs.
Logging the IP address from where a session originates is directly part of what's expected under this clause.

ISO/IEC 27002:2022 – Clause 5.15 (Logging Monitoring and Review)

You must monitor and review log data to detect anomalous behavior, which includes unexpected source IPs or geolocations.

ISO/IEC 27001 Annex A Controls (Mapped)

A.12.4.1 (prior to 2022 revision): “Event logging” – specifically refers to logging events that might be used to trace unauthorized activities.

Under the 2022 version, it's A.5.12 and A.5.15.
@Patrick010 commented on GitHub (Jul 22, 2025): > > Basic security origin tracebility. > > Where did you read this? ISO/IEC 27002:2022 – Clause 5.12 (Event Logging) It requires logs to: Record user activities, exceptions, and information security events. Include sufficient information to trace actions to individuals. This explicitly includes session source identifiers like IP addresses, timestamps, and user IDs. Logging the IP address from where a session originates is directly part of what's expected under this clause. ISO/IEC 27002:2022 – Clause 5.15 (Logging Monitoring and Review) You must monitor and review log data to detect anomalous behavior, which includes unexpected source IPs or geolocations. ISO/IEC 27001 Annex A Controls (Mapped) A.12.4.1 (prior to 2022 revision): “Event logging” – specifically refers to logging events that might be used to trace unauthorized activities. Under the 2022 version, it's A.5.12 and A.5.15.
Author
Owner

@bakerboy448 commented on GitHub (Jul 22, 2025):

Let's take a hypothetical usecase since you refused to provided any. There is no reasonable use case where this functionality makes sense to add as noted a few times.

  • Jellyseerr adds movie to Radarr. Radarr logs the upstream UA and IP if provided
  • X time passes. Radarr finally grabs and imports a release
  • connection enabled to Jellyfin - scan sent to Jellyfin

There's no relation between bullet 1 and bullet 3 and thus no relevant information to pass along to Jellyfin that jellyseerr added a movie months/weeks/years/days/hours ago.

No plans to pass along arbitrary headers from the add movie - or any - action to downstream Connections. No benefit is provided. No security implications exist and changing this would actually create a security issue and obfuscate that radarr actually made the calls.

@bakerboy448 commented on GitHub (Jul 22, 2025): Let's take a hypothetical usecase since you refused to provided any. There is no reasonable use case where this functionality makes sense to add as noted a few times. - Jellyseerr adds movie to Radarr. Radarr logs the upstream UA and IP if provided - X time passes. Radarr finally grabs and imports a release - connection enabled to Jellyfin - scan sent to Jellyfin There's no relation between bullet 1 and bullet 3 and thus no relevant information to pass along to Jellyfin that jellyseerr added a movie months/weeks/years/days/hours ago. No plans to pass along arbitrary headers from the add movie - or any - action to downstream Connections. No benefit is provided. No security implications exist and changing this would actually create a security issue and obfuscate that radarr actually made the calls.
Author
Owner

@Patrick010 commented on GitHub (Jul 22, 2025):

So much discussion to not send out the source IP. Laughable.

@Patrick010 commented on GitHub (Jul 22, 2025): So much discussion to not send out the source IP. Laughable.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/Radarr#9231
No description provided.