Nginx反向代理Yesod时客户端真实IP无法正常转发问题
Your Nginx configuration looks perfectly valid—you're correctly passing the real client IP via X-Real-IP and X-Forwarded-For headers. The issue is that Yesod (and its underlying WAI framework) doesn't automatically read these headers by default; it will log the IP of the direct connection source (which is Nginx's 127.0.0.1) unless you explicitly tell it to trust your reverse proxy and use the forwarded headers.
Here's how to fix this:
1. Use WAI's Forwarded Headers Middleware (Recommended)
The cleanest way is to use the forwardedHeadersMiddleware from the wai-extra package, which handles parsing forwarded IPs (and other headers like protocol) safely and correctly.
Steps:
- First, add
wai-extraas a dependency in your project's.cabalfile:build-depends: base >= 4.14 && < 5 , yesod >= 1.6 , wai-extra >= 3.1 -- Add this line -- ... other dependencies - Open your
Application.hsfile, import the middleware, and add it to your application stack:import Network.Wai.Middleware.ForwardedHeaders (forwardedHeadersMiddleware) makeApplication :: AppConfig DefaultEnv Extra -> IO Application makeApplication conf = do foundation <- makeFoundation conf app <- toWaiAppPlain foundation -- Wrap the app with the forwarded headers middleware return $ forwardedHeadersMiddleware app - This middleware automatically trusts requests coming from
127.0.0.1(your Nginx server) and replaces the logged remote IP with the value fromX-Forwarded-FororX-Real-IP.
2. Manually Extract Real IP in Custom Logging (Alternative)
If you prefer more control, you can modify Yesod's logging logic to directly pull the real IP from the request headers.
Example Custom Logger:
Open your Foundation.hs file and define a custom logger function:
import qualified Network.Wai as W import Data.ByteString.Char8 (unpack) import Data.Text (pack) -- Custom logger that prioritizes X-Real-IP over the direct connection IP myCustomLogger :: Yesod App => Logger -> LogSource -> LogLevel -> Text -> IO () myCustomLogger logger source level msg = do mReq <- getRequest let clientIp = case mReq of Just req -> -- First check X-Real-IP, fall back to direct remote host if missing case lookup "X-Real-IP" (W.requestHeaders req) of Just ipBs -> unpack ipBs Nothing -> show (W.remoteHost req) Nothing -> "unknown" -- Prepend the real IP to the log message let loggedMsg = pack $ "[" ++ clientIp ++ "] " ++ unpack msg defaultLogger logger source level loggedMsg
Then update your Yesod instance to use this logger:
instance Yesod App where -- ... other Yesod settings logger = myCustomLogger defaultLogger
3. Verify the Fix
After making either change, restart your Yesod application. Send a test request to your site, and check your Yesod logs—they should now show the real client IP instead of 127.0.0.1.
Note for Multi-Level Proxies
If you ever have multiple reverse proxies in front of Yesod, X-Forwarded-For will contain a comma-separated list of IPs. The forwardedHeadersMiddleware automatically handles this by selecting the first non-proxy IP in the list, which is more reliable than manual header parsing.
内容的提问来源于stack exchange,提问作者bg2000 Reinstate Monica

