2024-03-29T17:03:21 Status: #blog Tags: #altova #reverse-proxy #linux #nginx Links: [[Altova]] | [[MobileTogether]] | [[Reverse Proxy]] | [[Linux]] | [[Nginx]] | [[RecordsManager]] | [[AI]] # Setting up a Reverse Proxy for a MobileTogether Server Solution When you are setting up a [[MobileTogether]] Server in such a way that users will be able to access the site from a web browser (in addition to from MobileTogether client apps), you may sometimes want to hide the precise URL that is being used on the server to start a particular solution, which normally looks something like this: `https://server.name/run?d=/public/SolutionName This blog post discusses a convenient set up for the case where you want to hide the `run?d=...` part of the URL in a MobileTogether solution by deploying a reverse proxy server in front of the MobileTogether Server that is hosting your solution intended for public consumption. ![[afalk42_An_illustration_for_a_blog_post_on_setting_up_an_NGINX__8333f36f-b9b0-4576-bc72-6d3667700308.png]] ## The network configuration For the purpose of this blog post we're going to assume that you have deployed MobileTogether Server on Linux and are running a distribution similar to Ubuntu 22.0.4 LTS. So we can simply set up and configure the reverse proxy on the same virtual machine (VM) in your network's demilitarized zone (DMZ): ```mermaid flowchart LR; id1([Internet])-->id2{{Firewall}} id2{{Firewall}}-->ReverseProxy subgraph VM ReverseProxy--> MobileTogetherServer end ``` ## MT Server Configuration Furthermore, we're going to assume that the firewall is set up to just allow regular https traffic on port 443 to the VM running in your DMZ, so we'll configure NGINX to listen to that traffic on port 443. At the same time, we're going to set up MobileTogether Server to listen for regular http traffic on port 8083: ![[MobileClientPortsScreenshot2024-02-07.png]] The intent of using regular http between the reverse proxy and the MT Server is, of course, improved performance, since that connection is happening within the same VM, so there is no need to encrypt and decrypt the traffic between these two processes. However, we also keep the MT Server listening for https on port 8084, so that we can easily bypass the reverse proxy for testing or debugging purposes by accessing the VM via 8084 for un-proxied access vs 443 for proxied access. ## NGINX Reverse Proxy Configuration As a next step we need to install nginx on Ubuntu (`sudo apt install nginx`) and add a new site definition file to the `/etc/nginx/sites-available` directory, which we will call ```mtproxy``` - and I will explain the various sections of the file further below: ```nginx # # MobileTogether reverse proxy server configuration # # Created by: Alexander Falk # # Last modified: 3/29/2024 # # define reverse proxy upstream log format for improved logging log_format upstreamlog '[$time_local] $remote_addr - $remote_user - $server_name $host $request_uri to: $upstream_addr: $request $status upstream_response_time $upstream_response_time msec $msec request_time $request_time'; # server running on port 80 for http server { # http: redirect all requests immediately to https on same $host listen 80 default_server; listen [::]:80 default_server; server_name _; # server name is irrelevant because of default_server directive return 301 https://$host; } # server running on port 443 for https server { # https: configuration requires SSL certificates and protocols listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; # change the following paths to point to the ssl certificates for your domain ssl_certificate /var/opt/your_ssl_configuration/cert.pem; ssl_certificate_key /var/opt/your_ssl_configuration/key.pem; # enable only the recommended protocols and ciphers to ensure high security ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; ssl_prefer_server_ciphers on; server_name _; # server name is irrelevant because of default_server directive # reverse proxy root request to run?d= page on port 8083 of MT Advanced Server location = / { # the = sign specifies an exact match # but we also need to prevent the implicit automatic redirect to index.html index nothing_will_match; # this overrides default "index index.html" rule autoindex on; # this ensures that we stay in this location branch # specify special log format for improved logging of proxy requests access_log /var/log/nginx/access.log upstreamlog; # proxy the run?= page from the MT Advanced Server on port 8083 # on the same machine ( = localhost) proxy_pass; } # reverse proxy all other requests to port 8083 of MT Advanced Server # with request_uri intact so that all other resources can be served as is location / { # in this case without the = sign (see location above) # because we are matching any uri that starts with a /, so everything # specify special log format for improved logging of proxy requests access_log /var/log/nginx/access.log upstreamlog; # proxy the original $request_uri from the MT Advanced Server on # port 8083 on the same machine ( = localhost) proxy_pass$request_uri; } } ``` 1. Essentially, this site definition is configuring a web server to run on ports 80 (http) and 443 (https) like any normal website, but really implements a reverse proxy on 443 to the MT Advanced Server running on the same machine 2. The port 80 http server definition is very simple and essentially just issues a 301 redirect response to https: on the same $host – so irrespective of whether the server was accessed using its internal or external DNS name or via its external or internal IP address, the redirect goes to https on exactly the same server name (= $host) that the user had entered. The default_server directive used on the listen statements ensures that this server responds no matter what DNS name or IP address is being used. 3. The port 443 https server definition is a bit more involved, as this requires SSL configuration plus the actual reverse proxy: i.      We are again using default_server on the listen statement plus a nonsensical server name of _ to ensure this server responds irrespective of what host name is being used (this assumes you are not running multi-hosting on this machine, so it is one VM just set up for this one MT solution). ii.      For the SSL configuration we are using the recommended protocols and ciphers statement to ensure best practices. And, as expected, this earns us a solid A rating for the SSL Report from Qualys: ![[QualysResult2024-02-07.png]] iii.      Now we get to the actual essence of the reverse proxy configuration in the form of the two location {} blocks (for the purpose of this discussion, I have just removed the comments compared to the full configuration file above): ```nginx location = / { index nothing_will_match; autoindex on; access_log /var/log/nginx/access.log upstreamlog; proxy_pass; } ``` - This first location statement is the tricky one that took me “a little while” to figure out. The equal sign means exact match, so this matches only a request to / which is the root of the website, so just the hostname without anything further. That’s what we automatically want to internally proxy over to `run?d=/public/SolutionName` on the MT server. As it turns out – and despite what the documentation says – this = / rule will never trigger, because internally nginx defines an “index index.html” rule, and so a request to just / is internally and implicitly rewritten as /index.html and therefore doesn’t fall under the = / location, but the other one. After I finally understood why it doesn’t trigger, and searched for more specific solutions, I found this approach that will allow the = / location to actually work as intended: first we need to override the default index rule; and then we need to also make it stick to this block, which we can do by e.g. pretending we’ll provide a directory listing of all files (autoindex on). All that these two statement do, is force nginx to stay in this block and not invoke the automatic rewrite and go somewhere else. Then we can do the real work: to set the protocol and address of a proxied server and the uri to which the / location should be mapped: `proxy_pass;` So we proxy this to port 8083 (http) on the same machine (localhost = and use the `run?d=/public/SolutionName` uri to request the actual MT solution. The reason we use http instead of https here, is that this request doesn’t actually travel over the network, since it resolves on the localhost network stack internally, so there is no benefit to encrypting this request. On the contrary – it would take CPU resources to have MT Server encrypt it, and nginx decrypt it again (before ultimately re-encrypting it again when sending the response to the user’s browser). Furthermore, if we were to utilize https: we would need to map the request to a DNS name that is covered by the SSL certificate. That would still resolve to the same IP address, and thus be internally resolved rather than ever hit the physical network interface, but it is now dependent on the DNS server on the network being up and running. So it would add not just the cost of an extra encrypt/decrypt cycle, but also the cost of a DNS server request. That is why using http: and and http is much preferable in this situation. ```nginx location / { access_log /var/log/nginx/access.log upstreamlog; proxy_pass$request_uri; } ``` - This second location statement is very similar to the first, but without the = sign, so it matches any uri that begins with / i.e. all possible uris. This is essentially a catch-all location statement, and what it does is simply use the same localhost IP address and port 8083 as above, but this time it maps the original uri request as it was received: `proxy_pass$request_uri;` This is required so that all other web server GET and PUT requests that the MT web solution makes when it is running, as well as the loading of all JS and CSS files and graphics files are sent to the appropriate place on the MT server. - Common to both location {} blocks is also that we are changing the log format for access.log so that we get a bit more logging information on how the reverse proxy server is working, i.e. we’re logging the original request, where it was proxied to, and how long the upstream server (= MT Advanced Server) took to respond to the request. This should help aid us in debugging any possible issues going forward. 5. Next we need to enable the new configuration by adding a new symbolic link to the new site in the sites-available directory from the `/etc/nginx/sites-enabled` directory: `sudo ln -s /etc/nginx/sites-available/mtproxy /etc/nginx/sites-enabled/mtproxy` 6. If there is a symbolic link to the default site from the sites-enabled directory, which was automatically created as part of the initial nginx installation, then that needs to be deleted 7. And the last step is to simply restart the nginx server to activate the new reverse proxy configuration: `sudo service nginx reload` 8. At this point we can now monitor the access.log to see if the reverse proxy is working properly, while we access the site either using an internal DNS name, or using an external DNS name and coming in from the Internet through the firewall, and then performing some actions in the MT solution: `sudo tail -f /var/log/nginx/access.log` For an example of such a reverse proxy in action, you can visit our [[RecordsManager]] [[AI]] Demo website at [https://recordsmanager.ai.altova.com/](https://recordsmanager.ai.altova.com/) which I have discussed in more detail in this prior blog post: [[Creating a complete database solution from a single AI prompt]] --- # References - NGINX Reverse Proxy Documentation: https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/ - MobileTogether Server Documentation: https://www.altova.com/manual/MobileTogether/mobiletogetherserveradvanced/index.html - [[Creating a complete database solution from a single AI prompt]] - [[MobileTogether]] - [[Enterprise Solutions]] - [[Tools for Solution Providers]]