feat: add nginx reverse proxy for Plausible analytics#2595
Open
mroderick wants to merge 1 commit intocodebar:masterfrom
Open
feat: add nginx reverse proxy for Plausible analytics#2595mroderick wants to merge 1 commit intocodebar:masterfrom
mroderick wants to merge 1 commit intocodebar:masterfrom
Conversation
50f96fa to
ae07616
Compare
till
reviewed
Apr 25, 2026
00bffd1 to
2f6a70c
Compare
613c947 to
2d73a9a
Compare
- Proxy Plausible script and event API through nginx to bypass adblockers - Add DNS resolver (Quad9 9.9.9.9) for upstream domain resolution - Add proxy_cache in /dev/shm for Plausible script (5m TTL, 100m max) - Add security headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy) - Configure Puma to bind to Unix socket when nginx config exists - Signal nginx buildpack readiness via /tmp/app-initialized file ## Lessons learned Deviations from initial plan: - Used /dev/shm (tmpfs) for proxy_cache instead of /tmp (limited space) - Used ENV detection for nginx config file presence, not DYNO var (not available on all dynos) - Moved nginx 'set' directives inside server block (required by nginx) - Had to add DNS resolver manually (Heroku's resolver not automatically available in proxy_pass) Key technical discoveries: - heroku-buildpack-nginx waits for /tmp/app-initialized file before starting nginx - nginx 'set' directive only works in server/location contexts, not http block - Puma must bind to unix socket AND signal readiness for nginx to connect - proxy_cache in /dev/shm persists across dyno restarts (vs /tmp which doesn't) See: github.com/codebar/discussions/2580
891a726 to
df5e4e4
Compare
till
approved these changes
Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements the proposal from #2580 to run nginx as a reverse proxy in front of Rails on Heroku. This achieves two primary goals:
Changes
Infrastructure
heroku-community/nginxbuildpack configuration (config/nginx.conf.erb)Procfileto usebin/start-nginxwrapperconfig/puma.rb)Analytics
/js/script.js,/api/event)Proxy Caching
/dev/shm(memory-backed, faster than disk)Security Headers
Implementation Notes
What we learned (deviations from initial plan)
/dev/shm for proxy_cache — Initial plan suggested /tmp, but /dev/shm is a memory-backed tmpfs which is faster than disk-backed /tmp. Both are ephemeral and cleared on dyno restart.
Detection method — Initial plan used ENV["DYNO"], but this isn't set on all dyno types. Used File.exist?("config/nginx.conf.erb") instead to detect when nginx config is present.
Nginx 'set' directive — Initially placed in http block (lines 23-24), but nginx only allows 'set' inside server or location blocks. Had to move into server context.
DNS resolver — Heroku handles DNS for the main app, but nginx needs explicit resolver for upstream proxy_pass domains (plausible.io). Added Quad9 (9.9.9.9) as recommended by Plausible.
Key technical discoveries
/tmp/app-initializedfile before starting nginx. Puma must create this file after binding to socket.Security filters not included
The initial proposal included security filters (blocking malicious UAs, attack paths). These were removed to simplify the implementation and reduce maintenance burden. Security headers provide most of the benefit with minimal complexity.
Deployment
One-time setup: Add nginx buildpack
The nginx buildpack must be added BEFORE the Ruby buildpack so it runs AFTER to serve the final response:
Expected output after adding:
Deploy
Testing Checklist
Plausible Proxy:
/js/script.jsreturns Plausible script with correct content-type (200 OK)/api/eventaccepts POST and forwards to Plausible ({ok})X-Cache: MISSX-Cache: HITSecurity Headers:
Rails Functionality:
Rollback
If issues arise:
References