Skip to content

Commit ae07616

Browse files
committed
feat: add nginx reverse proxy for Plausible and security filtering
- Add nginx configuration with Plausible proxy routes and security filters - Configure Puma to bind to Unix socket with restricted permissions - Update Procfile to use bin/start-nginx wrapper - Update Plausible snippet to use proxied endpoints (/js/script.js, /api/event) Security features: - Block malicious user agents (Nikto, sqlmap, etc.) with HTTP 444 - Block common attack paths (WP admin, .env, .git, etc.) - Add security headers: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy - Set proxy timeouts and request size limits
1 parent fd1a03b commit ae07616

4 files changed

Lines changed: 93 additions & 4 deletions

File tree

Procfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
release: bundle exec rake db:migrate
2-
web: bundle exec puma -C config/puma.rb
2+
web: bin/start-nginx bundle exec puma -C config/puma.rb

app/views/layouts/application.html.haml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
= csp_meta_tag
3232

3333
<!-- Privacy-friendly analytics by Plausible -->
34-
%script{ async: true, src: 'https://plausible.io/js/pa-PFruVsE_br97UUCRXE_6f.js' }
34+
%script{ async: true, src: '/js/script.js' }
3535
%script
3636
window.plausible = window.plausible || function() { (plausible.q = plausible.q || []).push(arguments) }, plausible.init = plausible.init || function(i) { plausible.o = i || {} };
37-
plausible.init()
37+
plausible.init({ endpoint: '/api/event' })
3838

3939
%body.no-js{ 'class': "#{params[:controller]}-#{params[:action]}", 'data-bs-no-jquery': 'true' }
4040
#top

config/nginx.conf.erb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
daemon off;
2+
worker_processes auto;
3+
4+
events {
5+
worker_connections 1024;
6+
}
7+
8+
http {
9+
charset utf-8;
10+
server_tokens off;
11+
12+
# Security headers (set at http level to apply to all responses)
13+
add_header X-Frame-Options "SAMEORIGIN" always;
14+
add_header X-Content-Type-Options "nosniff" always;
15+
add_header X-XSS-Protection "1; mode=block" always;
16+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
17+
18+
# Logs to stdout/stderr for Heroku
19+
log_format custom '$http_x_forwarded_for - $remote_user [$time_local] '
20+
'"$request" $status $body_bytes_sent '
21+
'"$http_referer" "$http_user_agent"';
22+
access_log /dev/stdout custom;
23+
error_log /dev/stderr;
24+
25+
# Plausible endpoints
26+
set $plausible_script_url https://plausible.io/js/pa-PFruVsE_br97UUCRXE_6f.js;
27+
set $plausible_event_url https://plausible.io/api/event;
28+
29+
# Proxy settings
30+
proxy_http_version 1.1;
31+
proxy_buffering on;
32+
proxy_connect_timeout 5s;
33+
proxy_send_timeout 5s;
34+
proxy_read_timeout 10s;
35+
36+
upstream app_server {
37+
server unix:/tmp/nginx.socket fail_timeout=0;
38+
}
39+
40+
server {
41+
listen <%= ENV["PORT"] %>;
42+
server_name _;
43+
keepalive_timeout 5;
44+
client_max_body_size 10m;
45+
46+
# Security: Block known bad user agents
47+
if ($http_user_agent ~* (nikto|sqlmap|nessus|acunetix|masscan|zgrab|gobuster|dirbuster)) {
48+
return 444;
49+
}
50+
51+
# Security: Block common attack paths
52+
location ~ ^/(\.env|\.git|\.htaccess|\.htpasswd|config\.js|wp-admin|wp-login|xmlrpc\.php|administrator|phpmyadmin|shell|cmd|backdoor|cgi-bin) {
53+
return 444;
54+
}
55+
56+
# Plausible: Proxy script.js
57+
location = /js/script.js {
58+
proxy_pass $plausible_script_url;
59+
proxy_set_header Host plausible.io;
60+
proxy_pass_header Cache-Control;
61+
proxy_buffering on;
62+
}
63+
64+
# Plausible: Proxy event API
65+
location = /api/event {
66+
client_max_body_size 1m; # Analytics events are tiny
67+
proxy_pass $plausible_event_url;
68+
proxy_set_header Host plausible.io;
69+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
70+
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
71+
proxy_set_header X-Forwarded-Host $host;
72+
proxy_buffering on;
73+
}
74+
75+
# Rails: All other requests
76+
location / {
77+
proxy_pass http://app_server;
78+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
79+
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
80+
proxy_set_header Host $http_host;
81+
proxy_redirect off;
82+
}
83+
}
84+
}

config/puma.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929
threads threads_count, threads_count
3030

3131
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
32-
port ENV.fetch("PORT", 3000)
32+
# On Heroku with nginx buildpack, bind to Unix socket instead of port
33+
if ENV["DYNO"]
34+
bind "unix:///tmp/nginx.socket?umask=0077" # Restrict socket permissions to owner only
35+
else
36+
port ENV.fetch("PORT", 3000)
37+
end
3338

3439
# Allow puma to be restarted by `bin/rails restart` command.
3540
plugin :tmp_restart

0 commit comments

Comments
 (0)