8

I'm trying to have a self hosted sourcegraph server being served on a subdirectory of my domain using a reverse proxy to add an SSL cert.

The target is to have http://example.org/source serve the sourcegraph server

My rewrites and reverse proxy look like this:

  location /source {
    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-Scheme $scheme;

    rewrite ^/source/?(.*) /$1 break;
    proxy_pass http://localhost:8108;
  }

The problem I am having is that upon calling http://example.org/source I get redirected to http://example.org/sign-in?returnTo=%2F

Is there a way to rewrite the response of sourcegraph to the correct subdirectory?

Additionally, where can I debug the rewrite directive? I would like to follow the changes it does to understand it better.

-- Edit:

I know my approach is probably wrong using rewrite and I'm trying the sub_filter module right now.

I captured the response of sourcegraph using tcpdump and analyzed using wireshark so I am at:

GET /sourcegraph/ HTTP/1.0
Host: 127.0.0.1:8108
Connection: close
Upgrade-Insecure-Requests: 1
DNT: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://example.org/
Accept-Encoding: gzip, deflate, br
Accept-Language: de,en-US;q=0.9,en;q=0.8
Cookie: sidebar_collapsed=false; 

HTTP/1.0 302 Found
Cache-Control: no-cache, max-age=0
Content-Type: text/html; charset=utf-8
Location: /sign-in?returnTo=%2Fsourcegraph%2F
Strict-Transport-Security: max-age=31536000
Vary: Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Trace: #tracer-not-enabled
X-Xss-Protection: 1; mode=block
Date: Sat, 07 Jul 2018 13:59:06 GMT
Content-Length: 58

<a href="/sign-in?returnTo=%2Fsourcegraph%2F">Found</a>.
Nick Snyder
  • 2,966
  • 1
  • 21
  • 23
Cookie
  • 113
  • 1
  • 1
  • 7
  • Sourcegraph CTO here. Are you still facing this issue? Most of our customers set up Sourcegraph on a separate domain/subdomain, rather than a subdirectory, but we're happy to help if that's not an acceptable workaround. Potentially we'd have to change around some of our routing logic to support this. Were you able to get it up and running or can we assist? – beyang Aug 06 '18 at 19:44
  • My latest progress was using apache2 to redirect the requests. I stopped when I had to redirect every resource and gave up, I was basically fiddling with decompressing the response and rewriting the headers. I'm still stuck at this point and use a reverse proxy to access basically. I'm aware that my case is for the minority of users but I am using sourcegraph for my personal projects and it's a nuisance it doesn't support sub directories for me. – Cookie Aug 09 '18 at 10:58
  • Thanks for the context! I've noted your use case in an issue filed here: https://github.com/sourcegraph/issues/issues/103. Feel free to comment on that issue. And thanks for using Sourcegraph—is there anything else we can do to improve it for you? – beyang Aug 09 '18 at 18:43

2 Answers2

15

Using rewrite here causes extra processing overhead and is totally unnecessary.

proxy_pass works like this:

proxy_pass to a naked url, i.e. nothing at all after domain/ip/port and the full client request uri gets added to the end and passed to the proxy.

Add anything, even just a slash to the proxy_pass and whatever you add replaces the part of the client request uri which matches the uri of that location block.

so if you want to lose the source part of your client request it needs to look like this:

location /source/ {
    proxy_pass http://localhost:8108/;
    .....
}

Now requests will be proxied like this:

example.com/source/ -> localhost:8108/

example.com/source/files/file.txt -> localhost:8108/files/file.txt

It's important to point out that Nginx isn't just dropping /source/ from the request, it's substituting my entire proxy_pass URI, It's not as clear when that's just a trailing slash, so to better illustrate if we change proxy_pass to this:

proxy_pass http://localhost:8108/graph/; then the requests are now processed like this:

example.com/source/ -> localhost:8108/graph/

example.com/source/files/file.txt -> localhost:8108/graph/files/file.txt

If you are wondering what happens if someone requests example.com/source this works providing you have not set the merge_slashes directive to off as Nginx will add the trailing / to proxied requests.

Murat Baştaş
  • 156
  • 1
  • 8
miknik
  • 5,748
  • 1
  • 10
  • 26
  • I tried that before as you can see in the tcpdump segment sourcegraph specifically redirects to the root directory, that is the main problem I'm encountering. I'm trying to use sub filter to rewrite those redirects right now. – Cookie Jul 07 '18 at 17:12
  • In your tcp dump you are requesting `GET /sourcegraph/` – miknik Jul 07 '18 at 17:19
  • Maybe Im misunderstanding, but aren't you trying to keep `/source` in the url displayed in the client browser? Because this will do exactly the opposite of that `rewrite ^/source/?(.*) /$1 break;` – miknik Jul 07 '18 at 17:26
  • Yes, I have a few different sub directories in nginx to test different settings while maintaining some for later use. There is virtually no difference except it's another string. I just tested location /sourcegraph/ { proxy_pass http://127.0.0.1:8108/; } and it's exactly the same as in the tcpdump – Cookie Jul 07 '18 at 17:27
  • Delete the rewrite then and add a slash to the end of your proxy_pass. Your rewrite directive literally says capture everything after `/source/` and rewrite the url without `/source/` in it. – miknik Jul 07 '18 at 17:38
  • I didn't use a rewrite as of the last test. Just what I commented above. Adding Slashes doesn't fix it. Sourcegraph itself ignores the subdirectory and redirects. The problem isn't the proxy. – Cookie Jul 07 '18 at 18:17
  • Ah I get it. You probably need to add the subdirectory to your `"appURL": "https://domain.example.com"` setting in souregraph – miknik Jul 07 '18 at 18:26
  • @miknik You saved my day!! – javal88 Dec 14 '18 at 15:12
  • Although I got here while searching for a different application: There seems to be a difference between `proxy_pass http://MY_UPSTREAM` and `proxy_pass http://MY_UPSTREAM/` (note the last forward slash). Without the slash nginx forwarded the whole path INCLUDE my location. With the slash nginx remove the location path from the path forwarded to the other webserver. – Sebastian Grunow Dec 29 '22 at 15:56
12

If you have Nginx in front of another webserver that's running on port 8108 and serve its content by proxy_pass of everything from a subdir, e.g. /subdir, then you might have the issue that the service at port 8108 serves an HTML page that includes resources, calls its own APIs, etc. based on absolute URL's. These calls will omit the /subdir prefix, thus they won't be routed to the service at port 8108 by nginx.

One solution is to make the webserver at port 8108 serve HTML that includes the base href attribute, e.g

<head>
  <base href="https://example.com/subdir">
</head>

which tells a client that all links are relative to that path (see https://www.w3schools.com/tags/att_base_href.asp)

Sometimes this is not an option though - maybe the webserver is something you just spin up provided by an external docker image, or maybe you just don't see a reason why you should need to tamper with a service that runs perfectly as a standalone. A solution that only requires changes to the nginx in front is to use the Referer header to determine if the request was initiated by a resource located at /subdir. If that is the case, you can rewrite the request to be prefixed with /subdir and then redirect the client to that location:

location / {
    if ($http_referer = "https://example.com/subdir/") {
        rewrite ^/(.*) https://example.com/subdir/$1 redirect;
    }

    ...
}

location /subdir/ {
    proxy_pass http://localhost:8108/;
}

Or something like this, if you prefer a regex to let you omit the hostname:

if ($http_referer ~ "^https?://[^/]+/subdir/") {
    rewrite ^/(.*) https://$http_host/subdir/$1 redirect;
}
Kent Munthe Caspersen
  • 5,918
  • 1
  • 35
  • 34
  • 3
    I have been struggling with this for a very long time and your comment solved it. I was putting a django into a subdir and could not get it to work exactly because of the problem you describe. Merci. – Bastiaan Wakkie May 26 '21 at 15:33
  • 1
    Thanks a lot. This approach was exactly what I was looking for for a long time. – Tiemo Vorschütz Feb 08 '22 at 18:23
  • I really feel like this is the issue in my case, but for some reason neither block of config is working :( though yes I did update subdir etc for my usecase. – Caleb Jay Mar 01 '22 at 08:37
  • Ah, I think the reason this is failing for me is that this works in getting the app to load its javascript, but then the javascript changes the client URL directly, without initiating a request, to root/login, so it doesn't get picked up and forced to change to root/mysubdirectory/login. The subsequent requests then have a http_referer of root, minus `mysubdirectory`. Frustrating! – Caleb Jay Mar 02 '22 at 01:38