GeoServer Behind Nginx: Public and Private Access with HTTPS
A practical guide to reverse proxying GeoServer through multiple endpoints, handling CSRF/origin validation, authentication, and troubleshooting
Table of Contents
- Introduction
- Architecture Overview
- Nginx Reverse Proxy Configuration
- GeoServer Proxy Configuration
- Supporting Multiple Access Endpoints
- Public-Only Authentication
- GeoServer CSRF and Origin Validation
- Restarting GeoServer Correctly
- Troubleshooting Flow
- Common Errors and Fixes
- Production Checklist
- Quick Reference
1. Introduction
GeoServer is commonly deployed behind Nginx to provide HTTPS termination, reverse proxying, authentication, request limits, and centralized access control.
A particularly useful production architecture is to expose the same GeoServer through two different entry points:
Private network│▼Private endpoint│└──────────────┐│▼Nginx│▼GeoServer▲│┌──────────────┘│Public Internet│▼Public hostname
This allows internal users to access GeoServer through the private network while external users access it through a public hostname.
The two endpoints can have different security policies.
For example:
| Endpoint | HTTPS | Nginx authentication |
|---|---|---|
| Private | Yes | Optional |
| Public | Yes | Basic Auth |
The important part is that both endpoints ultimately reach the same GeoServer instance.
2. Architecture Overview
2.1 Recommended architecture
┌─────────────────────┐│ INTERNET │└──────────┬──────────┘││ HTTPS▼┌─────────────────────┐│ NGINX ││ :443 │└──────────┬──────────┘│┌─────────────────┴─────────────────┐│ │▼ ▼┌──────────────────┐ ┌──────────────────┐│ Public endpoint │ │ Private endpoint ││ │ │ ││ Basic Auth │ │ Optional Auth │└────────┬─────────┘ └────────┬─────────┘│ │└────────────────┬─────────────────┘││ HTTP▼┌─────────────────────┐│ GeoServer ││ :8080 │└─────────────────────┘
The important concept is:
Nginx provides multiple access paths; GeoServer remains a single backend service.
2.2 Request flow
A public request:
Browser││ HTTPS▼Nginx :443││ X-Forwarded-* headers▼GeoServer :8080
A private request follows the same backend path:
Private Client││ HTTPS▼Nginx :443│▼GeoServer :8080
GeoServer therefore needs to understand that the original request was HTTPS even though its connection from Nginx is HTTP.
3. Nginx Reverse Proxy Configuration
The basic GeoServer location should look like:
location /geoserver/ {proxy_pass http://127.0.0.1:8080/geoserver/;proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;proxy_redirect off;proxy_connect_timeout 1h;proxy_send_timeout 1h;proxy_read_timeout 1h;}
Why these headers matter
proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;
tell GeoServer about the original request.
Without them, GeoServer may think the external URL is something like:
http://127.0.0.1:8080/geoserver
instead of:
https://public-host/geoserver
4. GeoServer Proxy Configuration
GeoServer must also be configured to understand the reverse proxy.
In:
WEB-INF/web.xml
or the GeoServer configuration where appropriate, make sure the proxy configuration is consistent with the Nginx headers.
In global.xml, the relevant configuration can be:
<proxyBaseUrl>${X-Forwarded-Proto}://${X-Forwarded-Host}/geoserver</proxyBaseUrl>
and:
<useHeadersProxyURL>true</useHeadersProxyURL>
This creates the relationship:
Nginx:X-Forwarded-Proto: httpsX-Forwarded-Host: public-hostX-Forwarded-Port: 443↓GeoServer:https://public-host/geoserver
This becomes particularly important for the GeoServer Web UI, Wicket AJAX requests, redirects, generated links, and service URLs.
5. Supporting Multiple Access Endpoints
Use separate Nginx server blocks.
Private endpoint
server {listen 443 ssl;server_name PRIVATE_ENDPOINT;ssl_certificate /path/to/certificate.crt;ssl_certificate_key /path/to/private.key;location /geoserver/ {proxy_pass http://127.0.0.1:8080/geoserver/;proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;proxy_redirect off;}}
Public endpoint
server {listen 443 ssl;server_name PUBLIC_HOSTNAME;ssl_certificate /path/to/certificate.crt;ssl_certificate_key /path/to/private.key;location /geoserver/ {auth_basic "Restricted GeoServer";auth_basic_user_file /etc/nginx/.htpasswd;proxy_pass http://127.0.0.1:8080/geoserver/;proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;proxy_redirect off;}}
This provides:
Private↓Nginx↓GeoServerPublic↓Nginx↓Basic Auth↓GeoServer
6. Public-Only Authentication
A useful security model is to protect only the public endpoint with Nginx Basic Auth.
auth_basic "Restricted GeoServer";auth_basic_user_file /etc/nginx/.htpasswd;
Place these directives inside the public GeoServer location.
Do not put them in a global configuration if private users should bypass that authentication.
For example:
location /geoserver/ {auth_basic "Restricted GeoServer";auth_basic_user_file /etc/nginx/.htpasswd;proxy_pass http://127.0.0.1:8080/geoserver/;}
The private endpoint can omit auth_basic.
7. GeoServer CSRF and Origin Validation
This is one of the most important parts of a multi-endpoint GeoServer deployment.
GeoServer's Web UI performs many AJAX requests.
For example:
GET /geoserver/web/...
may work correctly while:
POST /geoserver/web/wicket/...
returns:
400 Bad Request
with:
Origin does not correspond to request
This is a strong indication that the request is being rejected by origin/CSRF validation rather than by the Nginx proxy itself.
7.1 Why this happens
The browser sends an origin such as:
Origin: https://public-host
or:
Origin: https://private-endpoint
GeoServer must recognize the legitimate endpoint.
When GeoServer is accessed through multiple hostnames or addresses, all legitimate endpoints need to be considered.
7.2 Configure the CSRF whitelist
In GeoServer's:
WEB-INF/web.xml
configure the trusted endpoints:
<context-param><param-name>GEOSERVER_CSRF_WHITELIST</param-name><param-value>PRIVATE_ENDPOINT,PUBLIC_HOSTNAME</param-value></context-param>
Use the actual trusted endpoints for your environment.
Do not add arbitrary origins.
8. Restarting GeoServer Correctly
This was one of the key lessons from troubleshooting.
Changing:
WEB-INF/web.xml
does not automatically reload the configuration into an already-running Java process.
After modifying the configuration:
sudo systemctl restart geoserver
Then verify:
sudo systemctl status geoserver
and:
ps aux | grep '[g]eoserver'
If GeoServer is systemd-managed
Prefer:
sudo systemctl restart geoserver
over manually running:
./shutdown.sh./startup.sh
The systemd service may have its own:
- Java options
- environment variables
- Jetty configuration
- working directory
- data directory
- stop port
- user account
Therefore, always determine how the service is actually running.
systemctl status geoserver
is a good first check.
9. Troubleshooting Flow
When something fails, use this order.
Request fails│▼Does the page load?/ \YES NO│ │▼ ▼AJAX/POST? Check Nginx/ \ server blockYES NO│ │▼ ▼Check 400 Check proxyresponse configuration│▼"Origin does not correspond?"│YES│▼Check CSRF whitelist│▼Restart GeoServer│▼Test again
Step 1 — Test Nginx
sudo nginx -t
Expected:
syntax is oktest is successful
Then:
sudo systemctl reload nginx
Step 2 — Check which server block is being used
sudo nginx -T
Look for the relevant:
server_name
Make sure the requested hostname is being handled by the intended server block.
Step 3 — Test GeoServer directly
From the server:
curl -I http://127.0.0.1:8080/geoserver/
If this works but the Nginx URL doesn't, focus on Nginx.
If this fails too, focus on GeoServer/Jetty.
Step 4 — Check the browser request
Open DevTools → Network.
Look at:
Request URLRequest MethodStatus CodeOriginRefererHost
A useful distinction is:
GET → 200POST → 400
This often indicates an application-level validation issue rather than basic proxy connectivity.
Step 5 — Check GeoServer logs
For a systemd-managed installation:
sudo journalctl -u geoserver -f
Then reproduce the error.
10. Common Errors and Fixes
Error: 400 Origin does not correspond to request
Likely cause
GeoServer's origin/CSRF validation doesn't recognize the endpoint being used.
Check
WEB-INF/web.xml
for:
GEOSERVER_CSRF_WHITELIST
Fix
Add the legitimate private/public endpoints and restart:
sudo systemctl restart geoserver
Error: GeoServer redirects to the wrong hostname
Possible cause
GeoServer doesn't have the correct reverse-proxy information.
Check Nginx:
proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;
Check GeoServer:
<proxyBaseUrl>${X-Forwarded-Proto}://${X-Forwarded-Host}/geoserver</proxyBaseUrl>
Error: Public Basic Auth keeps asking for credentials
Check whether:
auth_basic
is being applied to the correct location.
Also verify that there aren't conflicting Nginx server blocks handling the same hostname.
Test:
sudo nginx -T
Error: Private endpoint unexpectedly asks for Basic Auth
Check that auth_basic wasn't inherited from a higher-level block.
It should only be configured where required.
Error: 403 Forbidden
If the configuration contains:
allow ...deny all;
remember that Nginx access control is evaluated before the request reaches GeoServer.
Temporarily remove IP restrictions while diagnosing whether the problem is:
Nginx access control
or:
GeoServer
Error: nginx: conflicting server name
Example:
conflicting server name ... on 0.0.0.0:443
This means multiple server blocks are competing for the same hostname/listener combination.
Find duplicates:
sudo nginx -T | grep -n "server_name"
Then remove or consolidate the conflicting configuration.
11. Production Checklist
Before deploying, verify each item.
Nginx
-
nginx -tsucceeds -
Private and public endpoints have the intended
server_name - HTTPS certificates are correct
- GeoServer proxy location is correct
-
X-Forwarded-*headers are configured -
proxy_redirect offis appropriate - Timeouts are appropriate for long-running GeoServer operations
- Upload limits are appropriate
- Public authentication is applied only where intended
-
No conflicting
server_namedeclarations exist
GeoServer
- GeoServer is running under the expected service
-
Correct
GEOSERVER_DATA_DIRis being used -
proxyBaseUrlis correct -
useHeadersProxyURLis enabled when using forwarded headers - Legitimate endpoints are included in CSRF configuration
-
GeoServer is restarted after
web.xmlchanges - GeoServer logs are accessible
Testing
Test both:
Private endpoint├── Login├── GeoServer Web UI├── Create/edit style├── Upload├── AJAX operations└── REST/API operationsPublic hostname├── Nginx authentication├── GeoServer Web UI├── Create/edit style├── AJAX operations└── REST/API operations
Most importantly, don't test only the initial page load.
Test an operation that generates a POST/AJAX request.
12. Quick Reference
Nginx
location /geoserver/ {proxy_pass http://127.0.0.1:8080/geoserver/;proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Port $server_port;proxy_redirect off;}
GeoServer
<proxyBaseUrl>${X-Forwarded-Proto}://${X-Forwarded-Host}/geoserver</proxyBaseUrl><useHeadersProxyURL>true</useHeadersProxyURL>
CSRF
<context-param><param-name>GEOSERVER_CSRF_WHITELIST</param-name><param-value>PRIVATE_ENDPOINT,PUBLIC_HOSTNAME</param-value></context-param>
Restart
sudo systemctl restart geoserver
Verify
sudo systemctl status geoserver
Nginx test
sudo nginx -tsudo systemctl reload nginx
GeoServer logs
sudo journalctl -u geoserver -f
Nginx configuration
sudo nginx -T
Final Debugging Rule
When maintaining a GeoServer installation behind Nginx, remember this simple rule:
Page doesn't load│▼Check Nginx / connectivity
but:
Page loads│└── specific Web UI action → 400│▼Inspect Origin / Referer│▼"Origin does not correspond"│▼Check GeoServer CSRF│▼Restart GeoServer
This distinction is especially valuable in production because it prevents repeatedly changing a working Nginx reverse-proxy configuration when the actual problem is inside GeoServer's request validation.
