Skip to content

Server deployment architecture

See also: deployment/deployment-guide.md is the consolidated, environment-by-environment deployment guide that complements this architectural reference. For the AWS automation proposal that replaces today's console-clicking provisioning, see deployment/aws-cdk-automation.md.

TQPro deployment can take different variants. Here we describe the variants that are currently in use: - Local development (single machine) - Lab development & testing (not accessible from internet) - Lab multitenancy deployment (not accessible from internet) - AWS testing (accessible from internet) - AWS production

These installations differ in the server layout, network architecture layers and authentication access; the AWS testing and AWS production being almost equal.

Local development

Server Requirements

This is the simplest installation where everything works in place. The required infrastructure is: - Java compiler and runtime, with gradle support - Postgresql database - nginx web server

The local development is normally not subject to authentication, to avoid complexities of Keycloak. This is enabled by setting the appropriate property in tlinqapi.properties file:

#####
# Development mode - SECURITY WARNING: Only enable for local development!
# When enabled, allows API access without authentication.
# NEVER enable this in production environments!
# Default value if not set: false
dev-mode=false
If needed, Keycloak can also be installed (however, if you use Windows for development, this requires docker installation on Windows and appropriate network setup which can be difficult.)

In this setup, all servers can point to localhost (or appropriately provided name in /etc/hosts file for the local machine).

Configuration

Running the API server

To run the system on a development machine, you need to set up the local database with appropriate configuration in tourlinq.properties and tourlinq-config.xml. Once database is set up, the tqpro API server can be started from your development environment, gradle command line, or using the startup scripts; it is important that the environment variable TLINQ_HOME is configured to point to the execution directory, where the classpath is set and configuration files are accessible in the same directory.
For a good guidance, the shell script start_tlinq.sh shows how to set up the classpath, the directory layout and the environment variable passed to the server - it can be directly used in Linux environment or WSL.

Running the web interface

The web interface is located in the tqweb-adm project module. For fastest development (to avoid deployments), nginx can be configured to point the web root to the tqweb-adm directory - in that case, all changes will take place immediately without deployment.
The nginx configuration should also include the proxy to the API server so the web interface can execute the required APIs.

How it looks like

DB server     API server               Web Application             NGINX server
----------    ----------------         --------------------        ------------------
DB: tlinq      src/tqpro                src/tqpro                   server: dev.internal
Host: local      +---tqapi                +---tqweb-adm      <------- location '/'
                    +--Port 11080           +--index.html
                             ^----------------------------------------location '/tlinq-api/'

Lab development & testing deployment

Server Requirements

This setup is more complex and it includes multiple hosts (deployed as virtual machines). Following hosts are required:

Node Role Current hostname Listen port
Postgres DB server pgdb2.vanevski.net 5432
Keycloak server dev-auth01.vanevski.net 8080
API server dev-api01.vanevski.net 11080
Web server 1 dev-web01.vanevski.net 80
Web server 2 dev-web02.vanevski.net 80
Gateway 1 dev-gw01.vanevski.net 443
Gateway 2 dev-gw02.vanevski.net 443
Teamcity Build lab-cid.vanevski.net N/A

Configuration

One important note is that this environment, although is enabled and supports multi-tenant deployment, there is only a single tenant installed.

Database

The database server is standard PostgreSQL 16 server, where we need: - A platform database (tqplatform) - Tenant database (tlinq) for the only tenant on this environment

Keycloak configuration

On this node, keycloak is running as a bare-metal installation, serving 3 realms, out of which only one (tqpro-adm) is a functional realm and serves the TQPro web client (tqweb-adm), the admin API for the current tenant (tqpro-admin-api) and under the master realm, there is the tqpro-platform-admin client for tenant provisioning.
Other two realms are intended for: - tqweb-pub which is a public website, authentication at this point does not exist, but in the future it is intended for customer sign-on and access to personalized parts of the site; this is not functional at the moment. - tqweb-b2b, which is a future b2b site where b2b partners will be authenticated. This is not functional at the moment.

The keycloak listens to default port 8080.

API server

The API server on this environment runs as a standalone server (not clustered), and under OS control (not IDE or Gradle startup). It is deployed under /var/tqpro directory, with the following structure:

/var/tqpro/  
   +----- conf/ [<- Here we keep environment-dependent configuration ->]
           +----tlinqapi.properties
           +----tourlinq.properties
           +----odoo-server.properties
           +----log.properties
           +----amadeus.idfile
           +----ai-outline-prompt.txt
           +----templates/ [<- Here are the HTML templates for PDF generation>]
           +----outfiles/ [<- Here the output files are generated >]
           +----properties.d/
                +-----erp-booking.properties
                +-----messaging.properties
   +----- lib/
           +--- *.jar [<- all "external" (non-TQpro) JAR dependencies>]
   +----- api-NNN/ [<- This is the deployment directory where the TQPro is deployed, rewritten on deploy>]
           +----- tqapi.jar [<- the main JAR file for the API server >]
           +----- *.sh [<- startup, shutdown, maintenance scripts>]
           +----- *.xml [<- plugin config and app config files, environment-independent>]
           +----- *.html [<- various HTML templates for notifications and communication>]
           +----- templates/ [<- **symlink to ../conf/templates**>]
           +----- outfiles/ [<- **symlink to ../conf/outfiles**>]
           +----- properties.d/ [<- **symlink to ../conf/properties.d**>]
           +----- libext/ [<- **symlink to ../lib/**>]
           +----- tourlinq.properties [<- **symlink to ../conf/tourlinq.properties**>]
           +----- tlinqapi.properties [<- **symlink to ../conf/tlinqapi.properties**>]
           +----- log.properties [<- **symlink to ../conf/log.properties**>]
           +----- odoo-server.properties [<- **symlink to ../conf/odoo-server.properties**>]
           +----- amadeus.idfile [<- **symlink to ../conf/amadeus.idfile**>]
           +----- ai-outline-prompt.txt [<- **symlink to ../conf/ai-outline-prompt.txt**>]
   +----- api/ [<- symlink to the last API-NNN, NNN is the build number>]
Same structure is maintained on the other API servers as well (AWS test, AWS production).
The server startup and shutdown are managed by start_tlinq.sh and stop_tlinq.sh scripts.
Other important directories here are: - /var/log/tqpro, which is needed for the logging (can be configured in log.properties file).

Web Server

The web server is hosting: - The backend nginx service (routing to the relevant site directory) - The files of different web sites

Website file locations Website files (for all TQPro websites - public, b2b, admin) are deployed in release-numbered directories under /opt/web, with following structure;

/opt/web/
      +---- release-NNN [<-NNN is the build number>]
              +--- html-pub [<Public website - deployed from tqpro/tqweb-pub project module>]
              +--- html-b2b [<B2B website - deployed from tqpro/tqweb-b2b project module>]
              +--- html-adm [<Admin website - deployed from tqpro/tqweb-adm project module>]
      +---- release [<-symlink to latest release-NNN>]      

Backend NGINX configuration The backend nginx has a separate server block for each website; difference between the server blocks is the root location and the server name. Below is a sample config file for one server:

server {
    listen 80;
    server_name tqweb-adm.vanevski.net;

    client_max_body_size 20m;

    location / {
        root /var/www/html-adm;
        index index.html index.htm;
        try_files $uri $uri/ =404;
        proxy_buffers 16 64k;
        proxy_buffer_size 128k;

    }

    # In other environments, this block is in the gateway NGINX
    # ---------------------------------------------------------
    location /tlinq-api/ {
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT" always;
        add_header Access-Control-Max-Age "1000" always;
        add_header Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token, X-User, X-Roles" always;
        proxy_pass http://tqapi_backend_adm;
    }

}

upstream tqapi_backend_adm {
    server dev-api01.vanevski.net:11080;
}
Note that on this configuration (LAB), the backend nginx also proxies the calls to the API server; on the AWS configuration, that is moved to the gateway server so that the /tlinq/api/ location does not exist in the backend server.
The backend server always listens to port 80 (no SSL/TLS termination).

Gateway server

The gateway nginx server is the entry point to the system. It serves all incoming https requests, terminates SSL/TLS communications and routes the calls to appropriate upstreams. The SSL certificates are managed with certbot and updated in nginx configuration files.
Note for lab environments: as these environments are not accessible from internet, the certificates are not fetched from Let's Encrypt (certbot default), but from an internal server based on step-ca utility installed in the orchestration (build) host. This installation is explained in multitenancy-setup.md - Appendix B.

NGINX gateway configuration
On this configuration, we have multiple configuration blocks: - Upstream configuration block (placed in /etc/nginx/conf.d/upstreams.conf) that defines the web and API server upstream aliases for each of the servers - this is optional but advised to avoid repetition of upstream definitions in server blocks - One server block per TQPro website - One server block per authentication server (because keycloak must be accessible for authentication).

Sample server block for the tqweb-adm application on the lab host is shown below:

server {
    server_name tqweb-adm.vanevski.net;

    # Logging
    access_log /var/log/nginx/tqweb-adm.vanevski.net.access.log;
    error_log /var/log/nginx/tqweb-adm.vanevski.net.error.log;

    # Default location - proxy to dev app server
    location / {
        proxy_pass http://dev-web01.vanevski/net;
        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;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    client_max_body_size 50m;

    # TLinq API - proxy to api server on port 11080
    # This is currently configured in the web server so commented here.
    # In future, the block in the web server will be removed and this part will be uncommented.
    # location /tlinq-api/ {
    #    proxy_pass http://dev-api01.vanevski.net:11080;
    #    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;
    #
    #    # WebSocket support
    #    proxy_set_header Upgrade $http_upgrade;
    #    proxy_set_header Connection "upgrade";
    #
    #    # Timeouts
    #    proxy_connect_timeout 60s;
    #    proxy_send_timeout 60s;
    #    proxy_read_timeout 60s;
    #}

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/tqdev.peruntours.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/tqdev.peruntours.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = tqweb-adm.vanevski.net) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name tqweb-adm.vanevski.net;
    return 404; # managed by Certbot
}

Build server

The build server is a basic TeamCity build agent, connecting to the AWS-deployed TeamCity server. It is dedicated for lab builds and the build configurations are tailored for the local deployment. The server is accessing the local hosts for package deployment (the API and web servers) using usernames and passwords.

As a future requirement, and to adapt the server to more secure deployment even in the detached lab, the ssh config file needs to be used with hosts on which we should implement a dedicated deployment user and keyfiles for access (instead of passwords.)

For the purpose of the local environment, the build server will also serve as an orchestration server for multitenancy deployment, and this server will execute the multitenant provisioning.

Lab Multitenancy Deplyment

This environment is created specifically to test the multi-tenancy setup with tenant onboarding and management. It is a parallel environment in the lab setup, still disconnected from Internet and relying only in internal DNS/hosts setup and certificate authority.

TODO: This chapter to be populated after setting up the environment

AWS development + test environment

This is a pre-production setup where all integrations (internal+external ) exist and can be tested before the final production deployment. Server landscape is similar to the lab environment, but the service configuration is more secure and close to production setup, as this environment is exposed to the internet.

Server Requirements

This is a fully cloud-based setup on AWS EC2 and requires additional configuration specifically for the VPC and security zones, considering the different servers are deployed in different security zones. However, due to AWS cost optimizations, several development roles are allocated to a single instance (app01.dev.perunapps.com) that serves Odoo CRM, TQPro API and Admin website.
The gateway servers on AWS have a dual role as NAT instances, and have both a public DNS name and private (internal) DNS name.

Node Role Current hostname Security group Listen port
Postgres DB server pgdb01.dev.perunapps.com perun-sg-dev-db 5432
Keycloak server auth01.dev.perunapps.com perun-sg-dev, perun-sg-auth 8080
Odoo server app01.dev.perunapps.com perun-sg-dev 8069
Api server app01.dev.perunapps.com perun-sg-dev 11180
Web server app01.dev.perunapps.com perun-sg-dev 80
Gateway / NAT instance gw01.dev.perunapps.com / mgw01.perunapps.com perun-sg-public 443
Build Agent cid01.dev.perunapps.com perun-sg-dev N/A

For multi-instance deployment, there will be a new orchestration host added to the landscape.
TODO: add the information about the orchestration host.

Configuration

Database

The development database server on AWS is also a standard PostgreSQL server listening on port 5432. This server has the pgaudit extension installed.
The host is added to a tailnet (installed tailscale daemon, started when needed) which allows for access from the development workstations without SSH tunneling through the gateway - this is the more secure option as there is no inbound access required. Primary reason for this is fast deployment of DB migration scripts and access to DB for debugging purposes. This server does not have a public host name.
On this server, following databases are maintained: - Development / testing CRM database (odoo17) - Development TQPro database (tlinq) - Build server database (teamcity)

Keycloak configuration

Keycloak in the AWS dev environment is installed as a docker container; maps the local 8080 port directly to the container 8080 port and has same realm configuration as the lab host. It is publicly accessible through the gateway as https://authdev.perunapps.com with screenshots.

Application server configuration

Odoo CRM/ERP server
The Odoo server is installed in a python Virtual Environment, as a static application - it is not part of the deployment and it is managed separately. It is currently exposed as https://testcrm.perunapps.com and used by TQPro; there is an ambition to also use ERPNext as additional CRM integration (instead of Odoo) to demonstrate CRM agnosticity of the TQPro platform.

TQPro API server The TQPro API server here is installed in very similar way as in the lab environment - under the /var/tqpro directory, with the directory structure same as in the lab environment (numbered deployment directories, symlinks to environment-specific files). The server is not exposed externally - it is only accessed through the website, but the APIs can be reached with direct HTTP POST calls that will land on the same endpoint that the website is using.
The TQPro API server on this host listens to port 11180 to separate it from the earlier tlinq server that listened to 11080, which has been decommissioned but the configuration remains.

Web server
The web server here serves both the public and the admin website. The deployment structure is slightly different: the build configuration deploys the website files under the /var/tqpro tree:

/var/tqpro
  +--- api-NNN [<- The API deployment with build number suffix>]
  +--- conf [<-environment-specific conf files>]
  +--- web-NNN [<-TQPro admin site with build number suffix>]
  +--- pub-NNN [<-public site with build number suffix>]
  +--- api [<- symlink to api-NNN>]
  +--- web [<- symlink to web-NNN>]
  +--- pub [<- symlink to pub-NNN>]
Under the /var/www tree, there are additional symlinks to the directories in /var/tqpro:
/var/www
  +--- html [< Default nginx site - not used>]
  +--- html-bmh [< Website for BookMyHoliday - secondary brand for outbound tourism, public site>]
  +--- html-pub [<- Symlink to /var/tqpro/pub>]
  +--- html-adm [<- Symlink to /var/tqpro/web>]

The sites are configured in separate server blocks on the backend nginx under different server names: - html-bmh is configured as https://dev.bookmyholiday.ae - html-pub is configured as https://webdev.peruntours.com - html-adm is configured as https://tqdev.perunapps.com

Here is a sample of the server conf file for tqdev.perunapps.com; note that, unlike the lab server, the API upstream is not used here:

server {
      listen 80;
      server_name tqdev.peruntours.com;
      root /var/www/html-adm;
      index index.html index.htm;

      client_max_body_size 50m;

      location / {
          try_files $uri $uri/ =404;
      }
  }

Gateway configuration

The gateway server is the entrance point and SSL/TLS terminator with certbot-managed certificates. It is also a NAT instance and bastion host for the hosts in the developer subnets. It hosts the front-end nginx server, that is also configured with one server block per host. Here is a sample for the tqdev.perunapps.com site:

server {
    server_name tqdev.peruntours.com;

    # Logging
    access_log /var/log/nginx/tqdev.peruntours.com.access.log;
    error_log /var/log/nginx/tqdev.peruntours.com.error.log;

    # Default location - proxy to dev app server
    location / {
        proxy_pass http://app01.dev.perunapps.com;
        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;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    client_max_body_size 50m;

    # TLinq API - proxy to production app server on port 11180
    location /tlinq-api/ {
        proxy_pass http://app01.dev.perunapps.com:11180;
        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;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/tqdev.peruntours.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/tqdev.peruntours.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
    if ($host = tqdev.peruntours.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name tqdev.peruntours.com;
    return 404; # managed by Certbot
}

Build server configuration

The build server also hosts a standalone build agent, connecting to the TeamCity server, that builds and deploys the deliverables on the application server in specified directories. It uses server-side keyfiles to deliver files via SCP and log-in to hosts via SSH to finish the deployment and configuration.
The build server itself is not exposed; however, the same node also hosts the YouTrack project management application, exposed as https://track.perunapps.com.

Production environment

The production environment now hosts a running, single-tenant version of TQpro with the admin application and powering two public websites (peruntours.com and bookmyholiday.ae). It is deployed on AWS infrastructure. The setup is highly similar to the AWS development setup, with the difference that the Odoo CRM is on a separate application server, while the app and web servers are collocated. In addition, productio has its own build agent collocated with the Keycloak server due to the requirement to keep production and testbed separated without network connection between them.

Server requirements

Following nodes are used in production:

Node Role Current hostname Security group Listen port
Postgres DB server db01.prod.perunapps.com perun-sg-prod-db 5432
Keycloak server auth01.prod.perunapps.com perun-sg-prod-app, perun-sg-auth 8080
Build Agent auth01.prod.perunapps.com perun-sg-prod-app, perun-sg-auth N/A
Odoo server odoo.prod.perunapps.com perun-sg-prod-app 8069
Api server web01.prod.perunapps.com perun-sg-prod-app 11180
Web server web01.prod.perunapps.com perun-sg-prod-app 80
Gateway / NAT instance gw02.prod.perunapps.com / mgw02.perunapps.com perun-sg-public, perun-sg-prod-pub 443

Application server configuration

Database

The production database server on AWS is also a standard PostgreSQL server listening on port 5432. This server has the pgaudit extension installed. It holds following databases: - Odoo CRM database (odoo17) - TQPro server database (tlinq) - Keycloak database (keycloak)

The server is not accessible from anywhere except the perun-sg-prod-app security group (and ssh through the bastion host), and the migration scripts on this server are executed manually after copying the scripts there.

At the moment the automated backup is not set up, and the manual backup is done periodically - the automated backup should be configured as soon as possible.

Keycloak server

On this environment, Keycloak is installed as a bare-metal installation (no docker), set up as a systemd service and it uses the Postgresql server as a database backend. At this point, it only implements the tqpro-adm realm (in addition to the master realm), and within it, the tqweb-adm client for authenticating the admin application users.
Keycloak is exposed through the gateway as https://auth.perunapps.com. It does not implement MFA yet, which is planned for the future.

Build server

The build server is collocated with the Keycloak server, and runs a TeamCity build agent for building deployment of production builds. It is not exposed to the internet (other than the outbound connectivity through the gateway), and it connects to the TeamCity server externally.

CRM Server (Odoo)

CRM runs on a separate instance due to the load requirements; it is configured to start as a systemd service and executes under a python virtual environment. It is exposed through the gateway instance as https://crm.perunapps.com.
The CRM runs as a single instance, due to the known issues in Odoo multi-instance setup especially with the shared filesystem.

API server

The TQPro API server installation is exactly the same as the development server - with the same directory structure and symlinks - full installation under /var/tqpro directory:

/var/tqpro
  +--- lib [<- external libraries>]
  +--- api-NNN [<- The API deployment with build number suffix>]
     +--- tqapi.jar [<- TQPro API server JAR >]
     +--- lib [<- TQPro modules JAR files>]
     +--- libext [<- symlink to /var/tqpro/lib >]
     +--- *.sh scripts, *.xml config files, *.html template files, symlinks to ../conf/* files
  +--- conf [<-environment-specific conf files>]
  +--- api [<- symlink to api-NNN>]

Web server configuration

The web server is collocated with the API server in the same instance. Here the web sites are deployed under /var/www/rel-NNN directory (not under the /var/tqpro). Structure of the deployment is as follows:

/var/www/ [<-deployment root >]
      +--- bmh [<-bookmyholiday.ae website - not versioned >]
      +--- rel-NNN [<- deployment target for other sites, NNNis the build number>]
         +--- pub [<- public website, deployed from tqweb-pub module source>]
         +--- b2b [<- b2b website placeholder, deployed from tqweb-b2b module source >]
         +--- adm [<- adm website placeholder, deployed from tqweb-adm module source >]
      +--- html-pub [<- symlink to rel-NNN/pub, NNN - the latest build>]
      +--- html-b2b [<- symlink to rel-NNN/b2b, NNN - the latest build>]
      +--- html-adm [<- symlink to rel-NNN/pub, NNN - the latest build>]

The bmh, pub and adm sites are exposed through the gateway as: - bmh: https://www.bookmyholiday.ae - pub: https://www.peruntours.com - adm: https://admin.peruntours.com

Backend nginx configuration

The nginx server blocks poing to the html-pub, html-b2b, html-adm as root locations. Here is a sample of a backend server block:

server {
        listen 80;
        listen [::]:80;

        root /var/www/html-pub;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name peruntours.com www.peruntours.com;

        location / {
                try_files $uri $uri/ =404;
        }
}
The other server blocks have the same configuration.

Gateway configuration

The production gateway server is the entrance point and SSL/TLS terminator with certbot-managed certificates. It is also a NAT instance and bastion host for the hosts in the production subnets. It hosts the front-end nginx server, that is also configured with one server block per host. This is a sample of the server block for the admin website:

server {
    server_name admin.peruntours.com;
    root /var/www/maint/pub;

    # -------------------------------------------------------------------------
    # Certbot ACME Challenge
    # -------------------------------------------------------------------------
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    access_log  /var/log/nginx/admin.peruntours.com.access.log;
    error_log   /var/log/nginx/admin.peruntours.com.error.log;

    if ($http_user_agent ~* (go-http-client)) {
        return 403;
    }

    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
        application/javascript
        application/x-javascript
        application/json
        font/eot
        font/otf
        font/ttf
        image/bmp
        text/css
        text/javascript
        text/plain
        text/xml
        text/x-component
        text/x-cross-domain-policy;


    # These are now in LetsEncrypt includes
    #ssl_ciphers             HIGH:!ADH:!MD5;
    #ssl_protocols           SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    #ssl_session_timeout 30m;
    #ssl_prefer_server_ciphers on;

    proxy_buffers 16 64k;
    proxy_buffer_size 128k;
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;

    client_max_body_size 50m;

    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;

    location /tlinq-api/ {
            add_header Access-Control-Allow-Origin "*" always;
            add_header Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT" always;
            add_header Access-Control-Max-Age "1000" always;
            add_header Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token" always;

            proxy_pass http://apisrv;
    }

    location / {
            proxy_pass http://websrv;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/admin.peruntours.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/admin.peruntours.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    listen 80;
    listen [::]:80;
    server_name admin.peruntours.com;
}

server {
    if ($host = admin.peruntours.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name admin.peruntours.com;
    listen 80;
    return 404; # managed by Certbot
}

The upstream definitions are kept in /etc/nginx/conf.d/upstream.conf so they will be easily changeable in case of adding, removing or redirecting target servers.