The Oracle APEX Reverse Proxy Guide using NGINX

Just recently I talked about changing the /ords URL to something else, now I want to show another way to manipulate the URL.

I always disliked deploying multiple ORDS instances, just to provide different paths to the same DB. This happened out of legacy or SEO purposes so far.

Now let’s set up a simple reverse proxy to achieve any URL structure you want for your APEX server.

As reverse proxy server I chose to use NGINX (pronounced as engine-x), which is a free server comparable to Apache, and they also offer commercial support

For sake of this experiment we want to use OIDA instead of ORDS, hence have a URL of https://myserver.mydomain/oida/ instead of https://myserver.mydomain/ords/

Before we start, our server looks like that:

  • CentOS Server
  • Tomcat installed
  • ORDS deployed in Tomcat
  • APEX installed
  • NGINX installed

If you look for an easy way to set up a server like that, take a look at the OXAR project.

Anyway, here is the NGINX config you need to change ORDS to OIDA:

location /oida/ {
# this is the address and port of the ORDS installation
proxy_pass http://127.0.0.1:8080/ords/;

# set Origin to blank to avoid Chrome problems with CORS
proxy_set_header Origin "" ;

# pass along some header variables with the public host name/port/and so on
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host:$server_port;
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;

# this reverse proxies any "location" headers being passed in the response
proxy_redirect http://$host/ords/ https://$host/oida/;

# also tell cookies their public path
proxy_cookie_path /ords/ /oida/;

# reverse proxy links included in response (ie from ORDS webservice)
sub_filter_types application/json ;
sub_filter http://$host/ords/ https://$host/oida/;
sub_filter_once off;
}

Let me explain this line by line:

  1. Line 1: grabs only those requests having /oida
  2. Line 2: is a comment ;-)
  3. Line 3: the actual reverse proxy command saying that traffic is internally rerouted to localhost/ords/. Through the trailing slash after /ords/, NGINX is instructed to replace /oida/ with /ords/
  4. Line 6: Google Chrome enforces stricter CORS rules, than e.g. Firefox. By setting the Origin to blank we can make reverse proxying work, otherwise Chrome would block it
  5. Line 9-13: Pass some extra headers to ORDS, so that your app can now where this request originally comes from
  6. Line 16: In case ORDS or APEX sends a redirect as response header (location keyword), it gets translated from ORDS to OIDA (on https, if you want)
  7. Line 19: Also cookies need to know that they should be stored on /OIDA, rather than on /ORDS
  8. Line 22-24: I found this necessary when using ORDS RESTful webservices, to translate embedded links in the webservice answer to the public link (OIDA). It basically runs a search/replace on the reponse (if it is application/json) and swaps out internal links with the public ones. See this forum entry for details.

Be aware that NGINX loves its trailing semi-colons and doesn’t always say they are missing. Sometimes it simply ignores single directives just because of that. It also doesn’t show any warnings on a reload, so better use restart instead.

Here is another config of using /this/is/not/php/ instead of /ords/:

location /this/is/not/php/ {
# this is the address and port of the ORDS installation
proxy_pass http://127.0.0.1:8080/ords/;

# set Origin to blank to avoid Chrome problems with CORS
proxy_set_header Origin "" ;

# pass along some header variables with the public host name/port/and so on
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host:$server_port;
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;

# this reverse proxies any "location" headers being passed in the response
proxy_redirect http://$host/ords/ https://$host/this/is/not/php/;

# also tell cookies their public path
proxy_cookie_path /ords/ /this/is/not/php/ ;

# reverse proxy links included in response (ie from ORDS webservice)
sub_filter_types application/json ;
sub_filter http://$host/ords/ https://$host/this/is/not/php/;
sub_filter_once off;
}

That’s all you need for reverse proxying. In case you care about details, keep on reading.

Details and Background Info

Above configs are the result of a long process of trial and error. In theory reverse proxying is very simple: there is a public outside URL and some internal URL.

But life ain’t that easy, that is the reason we need a mix of multiple directives to achieve a rather straight forward goal. The reason for those multiple directives is that neither APEX nor ORDS have a way to deal with being reverse proxied, as opposed to other tools.

So does APEX create absolute links for redirecting by using the CGI variables REQUEST_PROTOCOL, HTTP_HOST, and SCRIPT_NAME. Those variables are ultimately set by Tomcat/ORDS and therefor reflect the internal server configuration, not the reverse proxy information. So will OWA_UTIL.get_cgi_env(‘SCRIPT_NAME’) always return ‘/ords’ no matter what you defined on your reverse proxy server (/oida or /this/is/not/php).

This APEX behavior is fixed by line 16.

Also ORDS acts the same way and uses the internal servers configuration to build links in their webservice results.

This ORDS behavior is fixed by lines 22-24.

Another gotcha is the Application Builder login. Up to APEX version 18.2 the internal APEX builder apps (ID range 4000-4999) use a cookie-path in their Authorization Schemes which is set to &CGI_SCRIPT_NAME. and that translates (see above) to /ords.

For now the fix for this behavior is a quick and painless UPDATE on this table to set the cookie path to NULL. Future versions of APEX will have this already fixed (according to internal APEX Dev Team sources…).

See it in Action

Here is a small GIF to prove it works.

15 thoughts on “The Oracle APEX Reverse Proxy Guide using NGINX

  1. Great post, thanks. We did it with Apache but was thinking to give Nginx a shot as it is a lovely, stable and light webserver. Any suggestions how to set an SSL certificate (as with Apache it was tricky on reverse proxy)

  2. Hi Omar,
    I’m sure you know how community works. Everyone gives a little and everyone can learn.

    Let’s say the Apache config is your homework. Please blog about it once you are done.
    Thank you,
    Peter

  3. Hi!
    I am trying to proxy-reverse “apex.oracle.com” with mixed results. Using httpd or nginx I am getting in both cases “Your session has expired” when I submit *my* forms (not builder’s). As far as I can understand, I have used your configuration in a correct way (ie: I do not have to care about updating the cookie path to null), but I think that I have problems with the cookies path any way and they are not getting through the proxy.

    Any advice?

    Thank you!

  4. Hi,

    We are unable to access reversproxy getting below error. could you please suggest on this.

    Contact your application administrator. Details about this incident are available via debug id “121”.

  5. Peter: is there any way of get relative URIs in Location header redirections with ORDS ?

    For example, through Apache mod_plsql use relative URLs in HTTP Location header field:

    alfonso@cantor:~$ curl -I -s https://host.domain/pls/app/f?p=104 | grep Location
    Location: f?p=104:101:

    This is specially useful for SSL offloading. But, with ORDS, the default behavior is to use complete absolute URI for redirection, e.g.:

    alfonso@cantor:~$ curl -I -s http://host.domain:8080/ords/f?p=104 | grep Location
    Location: http://host.domain:8080/ords/f?p=104:1::::::

    Problem is: when this application is behind a WAF the complete absolute URI for redirection breaks the force redirection from http to https

    Is there any way to make the behavior of redirections in ORDS be like that of Apache mod_plsql ?

    As far as we know, this is a part of HTTP 1.1 specification since IETF RFC 7231

  6. And if I want to set access a direct application (f?p=104) when open the location (i.e: open xxxxxxxx.com go to xxxxxxxxx.com/f?p=104)?

    server_name xxxxxxx.com

    location / {
    proxy_pass http://10.0.0.5:8080/ords/f?p=104;
    proxy_buffering off;
    proxy_set_header Origin “” ;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host:$server_port;
    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_redirect http://$host/ords/ http://$host/;
    proxy_cookie_path /ords/ /;
    sub_filter_types application/json ;
    sub_filter http://$host/ords/ http://$host/;
    sub_filter_once off;
    }

    Setting “proxy_pass http://10.0.0.5:8080/ords/f?p=104;” isn’t work, returns “Application with the alias “104F?P=104″ does not exist.”

    Can you help?

  7. Hey all,
    so far this process working fine for me.
    However using APEX 20.1 and when feature “Friendly URLs” is enabled the login flow not working anymore.
    The proxy_redirect not working correctly – seems to be related to the workspace name attached to the url e.g.

    mydomain.com/o/f?p=100 => login works without problem

    but when friendly url is enabled:

    mydomain.com/o/myspace/r/myapp/login => login not working with JS error message related to path not being proxied correctly =>
    https://mydomain.com/ords/wwv_flow.accept – 404 not found

    any ideas how to get this up and running with friendly urls enabled?

Leave a Reply

Your email address will not be published. Required fields are marked *