|  | 
|  | 1 | +# Additional MIME types that you'd like nginx to handle go in here | 
|  | 2 | +types { | 
|  | 3 | +  text/csv csv; | 
|  | 4 | +  application/wasm wasm; | 
|  | 5 | +} | 
|  | 6 | + | 
|  | 7 | +upstream discourse { | 
|  | 8 | +  server 127.0.0.1:3000; | 
|  | 9 | +} | 
|  | 10 | + | 
|  | 11 | +# inactive means we keep stuff around for 1440m minutes regardless of last access (1 week) | 
|  | 12 | +# levels means it is a 2 deep hierarchy cause we can have lots of files | 
|  | 13 | +# max_size limits the size of the cache | 
|  | 14 | +proxy_cache_path /var/nginx/cache inactive=1440m levels=1:2 keys_zone=one:10m max_size=600m; | 
|  | 15 | + | 
|  | 16 | +# Increased from the default value to acommodate large cookies during oAuth2 flows | 
|  | 17 | +# like in https://meta.discourse.org/t/x/74060 and large CSP and Link (preload) headers | 
|  | 18 | +proxy_buffer_size 32k; | 
|  | 19 | +proxy_buffers 4 32k; | 
|  | 20 | + | 
|  | 21 | +# Increased from the default value to allow for a large volume of cookies in request headers | 
|  | 22 | +# Discourse itself tries to minimise cookie size, but we cannot control other cookies set by other tools on the same domain. | 
|  | 23 | +large_client_header_buffers 4 32k; | 
|  | 24 | + | 
|  | 25 | +# attempt to preserve the proto, must be in http context | 
|  | 26 | +map $http_x_forwarded_proto $thescheme { | 
|  | 27 | +  default $scheme; | 
|  | 28 | +  "~https$" https; | 
|  | 29 | +} | 
|  | 30 | + | 
|  | 31 | +log_format log_discourse '[$time_local] "$http_host" $remote_addr "$request" "$http_user_agent" "$sent_http_x_discourse_route" $status $bytes_sent "$http_referer" $upstream_response_time $request_time "$upstream_http_x_discourse_username" "$upstream_http_x_discourse_trackview" "$upstream_http_x_queue_time" "$upstream_http_x_redis_calls" "$upstream_http_x_redis_time" "$upstream_http_x_sql_calls" "$upstream_http_x_sql_time"'; | 
|  | 32 | + | 
|  | 33 | +# Allow bypass cache from localhost | 
|  | 34 | +geo $bypass_cache { | 
|  | 35 | +  default         0; | 
|  | 36 | +  127.0.0.1       1; | 
|  | 37 | +  ::1             1; | 
|  | 38 | +} | 
|  | 39 | + | 
|  | 40 | +include conf.d/outlets/before-server/*.conf; | 
|  | 41 | + | 
|  | 42 | +server { | 
|  | 43 | +  access_log /var/log/nginx/access.log log_discourse; | 
|  | 44 | + | 
|  | 45 | +  include conf.d/outlets/server/*.conf; | 
|  | 46 | + | 
|  | 47 | +  gzip on; | 
|  | 48 | +  gzip_vary on; | 
|  | 49 | +  gzip_min_length 1000; | 
|  | 50 | +  gzip_comp_level 5; | 
|  | 51 | +  gzip_types application/json text/css text/javascript application/x-javascript application/javascript image/svg+xml application/wasm; | 
|  | 52 | +  gzip_proxied any; | 
|  | 53 | + | 
|  | 54 | +  server_name _; | 
|  | 55 | +  server_tokens off; | 
|  | 56 | + | 
|  | 57 | +  sendfile on; | 
|  | 58 | + | 
|  | 59 | +  keepalive_timeout 65; | 
|  | 60 | + | 
|  | 61 | +  # maximum file upload size (keep up to date when changing the corresponding site setting) | 
|  | 62 | +  client_max_body_size 10m; | 
|  | 63 | + | 
|  | 64 | +  # path to discourse's public directory | 
|  | 65 | +  set $public /var/www/discourse/public; | 
|  | 66 | + | 
|  | 67 | +  # without weak etags we get zero benefit from etags on dynamically compressed content | 
|  | 68 | +  # further more etags are based on the file in nginx not sha of data | 
|  | 69 | +  # use dates, it solves the problem fine even cross server | 
|  | 70 | +  etag off; | 
|  | 71 | + | 
|  | 72 | +  # prevent direct download of backups | 
|  | 73 | +  location ^~ /backups/ { | 
|  | 74 | +    internal; | 
|  | 75 | +  } | 
|  | 76 | + | 
|  | 77 | +  # bypass rails stack with a cheap 204 for favicon.ico requests | 
|  | 78 | +  location /favicon.ico { | 
|  | 79 | +    return 204; | 
|  | 80 | +    access_log off; | 
|  | 81 | +    log_not_found off; | 
|  | 82 | +  } | 
|  | 83 | + | 
|  | 84 | +  location / { | 
|  | 85 | +    root $public; | 
|  | 86 | +    add_header ETag ""; | 
|  | 87 | + | 
|  | 88 | +    # auth_basic on; | 
|  | 89 | +    # auth_basic_user_file /etc/nginx/htpasswd; | 
|  | 90 | + | 
|  | 91 | +    location ~ ^/uploads/short-url/ { | 
|  | 92 | +      proxy_set_header Host $http_host; | 
|  | 93 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 94 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 95 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 96 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 97 | +      proxy_pass http://discourse; | 
|  | 98 | +      break; | 
|  | 99 | +    } | 
|  | 100 | + | 
|  | 101 | +    location ~ ^/(secure-media-uploads/|secure-uploads)/ { | 
|  | 102 | +      proxy_set_header Host $http_host; | 
|  | 103 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 104 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 105 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 106 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 107 | +      proxy_pass http://discourse; | 
|  | 108 | +      break; | 
|  | 109 | +    } | 
|  | 110 | + | 
|  | 111 | +    location ~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$ { | 
|  | 112 | +      expires 1y; | 
|  | 113 | +      add_header Cache-Control public,immutable; | 
|  | 114 | +      add_header Access-Control-Allow-Origin *; | 
|  | 115 | +    } | 
|  | 116 | + | 
|  | 117 | +    location = /srv/status { | 
|  | 118 | +      access_log off; | 
|  | 119 | +      log_not_found off; | 
|  | 120 | +      proxy_set_header Host $http_host; | 
|  | 121 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 122 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 123 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 124 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 125 | +      proxy_pass http://discourse; | 
|  | 126 | +      break; | 
|  | 127 | +    } | 
|  | 128 | + | 
|  | 129 | +    # some minimal caching here so we don't keep asking | 
|  | 130 | +    # longer term we should increase probably to 1y | 
|  | 131 | +    location ~ ^/javascripts/ { | 
|  | 132 | +      expires 1d; | 
|  | 133 | +      add_header Cache-Control public,immutable; | 
|  | 134 | +      add_header Access-Control-Allow-Origin *; | 
|  | 135 | +    } | 
|  | 136 | + | 
|  | 137 | +    location ~ ^/assets/(?<asset_path>.+)$ { | 
|  | 138 | +      expires 1y; | 
|  | 139 | +      # asset pipeline enables this | 
|  | 140 | +      brotli_static on; | 
|  | 141 | +      gzip_static on; | 
|  | 142 | +      add_header Cache-Control public,immutable; | 
|  | 143 | +      # HOOK in asset location (used for extensibility) | 
|  | 144 | +      # TODO I don't think this break is needed, it just breaks out of rewrite | 
|  | 145 | +      break; | 
|  | 146 | +    } | 
|  | 147 | + | 
|  | 148 | +    location ~ ^/plugins/ { | 
|  | 149 | +      expires 1y; | 
|  | 150 | +      add_header Cache-Control public,immutable; | 
|  | 151 | +      add_header Access-Control-Allow-Origin *; | 
|  | 152 | +    } | 
|  | 153 | + | 
|  | 154 | +    # cache emojis | 
|  | 155 | +    location ~ /images/emoji/ { | 
|  | 156 | +      expires 1y; | 
|  | 157 | +      add_header Cache-Control public,immutable; | 
|  | 158 | +      add_header Access-Control-Allow-Origin *; | 
|  | 159 | +    } | 
|  | 160 | + | 
|  | 161 | +    location ~ ^/uploads/ { | 
|  | 162 | +      # NOTE: it is really annoying that we can't just define headers | 
|  | 163 | +      # at the top level and inherit. | 
|  | 164 | +      # | 
|  | 165 | +      # proxy_set_header DOES NOT inherit, by design, we must repeat it, | 
|  | 166 | +      # otherwise headers are not set correctly | 
|  | 167 | +      proxy_set_header Host $http_host; | 
|  | 168 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 169 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 170 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 171 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 172 | +      proxy_set_header X-Sendfile-Type X-Accel-Redirect; | 
|  | 173 | +      proxy_set_header X-Accel-Mapping $public/=/downloads/; | 
|  | 174 | +      expires 1y; | 
|  | 175 | +      add_header Cache-Control public,immutable; | 
|  | 176 | + | 
|  | 177 | +      ## optional upload anti-hotlinking rules | 
|  | 178 | +      #valid_referers none blocked mysite.com *.mysite.com; | 
|  | 179 | +      #if ($invalid_referer) { return 403; } | 
|  | 180 | + | 
|  | 181 | +      # custom CSS | 
|  | 182 | +      location ~ /stylesheet-cache/ { | 
|  | 183 | +          add_header Access-Control-Allow-Origin *; | 
|  | 184 | +          try_files $uri =404; | 
|  | 185 | +      } | 
|  | 186 | + | 
|  | 187 | +      # this allows us to bypass rails | 
|  | 188 | +      location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico||avif)$ { | 
|  | 189 | +          add_header Access-Control-Allow-Origin *; | 
|  | 190 | +          try_files $uri =404; | 
|  | 191 | +      } | 
|  | 192 | + | 
|  | 193 | +      # SVG needs an extra header attached | 
|  | 194 | +      location ~* \.(svg)$ { | 
|  | 195 | +      } | 
|  | 196 | + | 
|  | 197 | +      # thumbnails & optimized images | 
|  | 198 | +      location ~ /_?optimized/ { | 
|  | 199 | +          add_header Access-Control-Allow-Origin *; | 
|  | 200 | +          try_files $uri =404; | 
|  | 201 | +      } | 
|  | 202 | + | 
|  | 203 | +      proxy_pass http://discourse; | 
|  | 204 | +      break; | 
|  | 205 | +    } | 
|  | 206 | + | 
|  | 207 | +    location ~ ^/admin/backups/ { | 
|  | 208 | +      proxy_set_header Host $http_host; | 
|  | 209 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 210 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 211 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 212 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 213 | +      proxy_set_header X-Sendfile-Type X-Accel-Redirect; | 
|  | 214 | +      proxy_set_header X-Accel-Mapping $public/=/downloads/; | 
|  | 215 | +      proxy_pass http://discourse; | 
|  | 216 | +      break; | 
|  | 217 | +    } | 
|  | 218 | + | 
|  | 219 | +    # This big block is needed so we can selectively enable | 
|  | 220 | +    # acceleration for backups, avatars, sprites and so on. | 
|  | 221 | +    # see note about repetition above | 
|  | 222 | +    location ~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker) { | 
|  | 223 | +      proxy_set_header Host $http_host; | 
|  | 224 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 225 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 226 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 227 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 228 | + | 
|  | 229 | +      # if Set-Cookie is in the response nothing gets cached | 
|  | 230 | +      # this is double bad cause we are not passing last modified in | 
|  | 231 | +      proxy_ignore_headers "Set-Cookie"; | 
|  | 232 | +      proxy_hide_header "Set-Cookie"; | 
|  | 233 | +      proxy_hide_header "X-Discourse-Username"; | 
|  | 234 | +      proxy_hide_header "X-Runtime"; | 
|  | 235 | + | 
|  | 236 | +      # note x-accel-redirect can not be used with proxy_cache | 
|  | 237 | +      proxy_cache one; | 
|  | 238 | +      proxy_cache_key "$scheme,$host,$request_uri"; | 
|  | 239 | +      proxy_cache_valid 200 301 302 7d; | 
|  | 240 | +      proxy_cache_bypass $bypass_cache; | 
|  | 241 | +      proxy_pass http://discourse; | 
|  | 242 | +      break; | 
|  | 243 | +    } | 
|  | 244 | + | 
|  | 245 | +    # we need buffering off for message bus | 
|  | 246 | +    location /message-bus/ { | 
|  | 247 | +      proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 248 | +      proxy_set_header Host $http_host; | 
|  | 249 | +      proxy_set_header X-Real-IP $remote_addr; | 
|  | 250 | +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 251 | +      proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 252 | +      proxy_http_version 1.1; | 
|  | 253 | +      proxy_buffering off; | 
|  | 254 | +      proxy_pass http://discourse; | 
|  | 255 | +      break; | 
|  | 256 | +    } | 
|  | 257 | + | 
|  | 258 | +    # this means every file in public is tried first | 
|  | 259 | +    try_files $uri @discourse; | 
|  | 260 | +  } | 
|  | 261 | + | 
|  | 262 | +  location /downloads/ { | 
|  | 263 | +    internal; | 
|  | 264 | +    alias $public/; | 
|  | 265 | +  } | 
|  | 266 | + | 
|  | 267 | +  location @discourse { | 
|  | 268 | +    include conf.d/outlets/discourse/*.conf; | 
|  | 269 | +    proxy_set_header Host $http_host; | 
|  | 270 | +    proxy_set_header X-Request-Start "t=${msec}"; | 
|  | 271 | +    proxy_set_header X-Real-IP $remote_addr; | 
|  | 272 | +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 
|  | 273 | +    proxy_set_header X-Forwarded-Proto $thescheme; | 
|  | 274 | +    proxy_pass http://discourse; | 
|  | 275 | +  } | 
|  | 276 | +} | 
0 commit comments