Skip to content

Let’s Encrypt and NGINX

We’re going to be setting up NGINX to use LET’S ENCRYPT and for this example we’re going to be pointing to a Guacamole server, which requires zero buffering of packets as it is using real time RDP data. So the config will take that into account. Additionally, we’re shooting for the highest rating possible on SSL Labs and only supporting the latest encryption standards!

UPDATE: I’ve gotten two reports that the cron job is failing so I changed it to what is recommended by Juan in the comments below

We’re going to start with 2 assumptions:
#1. You have configured your external DNS to point to this server…
#2. You’re port forwarding ports 80 and 443 to this server…

Let’s set a few variables for our install

EXTERNALFQDN is the external fully qualified domains name we’re passing traffic for
INTERNALFQDN is the internal host name of your Guacamole server

Install NGINX and Let’s Encrypt:

apt-get -y install nginx letsencrypt openssl

Make a directory to store your certs

mkdir -p /etc/nginx/ssl/$EXTERNALFQDN

Create a 4096 bit Diffie-Hellman Key (Go big or go home!)

openssl dhparam -out /etc/nginx/ssl/$EXTERNALFQDN/dhparam.pem 4096

Now let’s configure NGINX. Careful of the formatting here. ssl_ciphers should be one line if you copy and paste

cat > /etc/nginx/nginx.conf <<- EOM
user www-data;
worker_processes 4;
pid /run/;

        worker_connections 768;

        # My Certificates
        #ssl_certificate /etc/nginx/ssl/$EXTERNALFQDN/fullchain.pem;
        #ssl_certificate_key /etc/nginx/ssl/$EXTERNALFQDN/privkey.pem;

        # SSL Performance Related
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        # SSL Protocols and Ciphers
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1.2;
        ssl_ciphers "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:!AES128:!aNULL:!MD5:!eNULL:!EXPORT:!DES:!PSK:!RC4";
        # DHE Key-Exchange
        ssl_dhparam /etc/nginx/ssl/$EXTERNALFQDN/dhparam.pem;

        # Random Security Stuff
        server_tokens off;
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header Strict-Transport-Security max-age=63072000;

        # Common Proxy Settings
        proxy_set_header Host      $host;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;

        # Default Config Stuff #
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 4096; #Default:2048
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        gzip on;
        gzip_disable "msie6";
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

                listen 80;
                listen [::]:80;
                server_name $EXTERNALFQDN;
                location ~ /.well-known/acme-challenge
                    root /var/www/html/;
                #return 301 https://$host$request_uri;

                listen 443 ssl;
                listen [::]:443 ssl;
                server_name $EXTERNALFQDN;

                proxy_buffering off;
                proxy_redirect  off;
                proxy_cookie_path /guacamole/ /;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                location ~ /.well-known/acme-challenge
                    root /var/www/html/;
                location /
                        proxy_pass http://$INTERNALFQDN:8080/guacamole/;

Restart NGINX

service nginx restart

Request a cert from Let’s Encrypt

letsencrypt certonly -a webroot --webroot-path=/var/www/html -d $EXTERNALFQDN --rsa-key-size 4096

Link the Let’s Encrypt public and private keys to the NGINX Config

ln -s /etc/letsencrypt/live/$EXTERNALFQDN/fullchain.pem /etc/nginx/ssl/$EXTERNALFQDN/fullchain.pem
ln -s /etc/letsencrypt/live/$EXTERNALFQDN/privkey.pem /etc/nginx/ssl/$EXTERNALFQDN/privkey.pem

Update the NGINX config to use those certs and enforce 443 with redirection

sed -i 's/#ssl_certificate/ssl_certificate/g' /etc/nginx/nginx.conf
sed -i 's/#return 301/return 301/' /etc/nginx/nginx.conf

Restart NGINX

service nginx restart

Now we need to setup a cron job that runs every Sunday to check for certs that are near expiration and renew them automatically!

(crontab -l 2>/dev/null; echo '@weekly (certbot renew && service nginx restart) 2>&1 >/dev/null') | crontab -
Published inTech


  1. Noel Noel

    Hi Chase-

    First of all Thanks for the awesome Guacamole install script. That worked wonders and I had a Guac server setup in no time.

    Now, I am following this document and I am running into a few issues.

    1. I couldn’t run the CAT command and I had to manually edit the nginx.conf file and add the data to it. No big deal it works.

    2. Environmental variables. I set them in the /etc/environment file and rebooted. Tested that they are there and I can ping from the box to the variables I set.

    When I go to restart NGINX after editing the file I get an error.

    Dec 06 09:37:02 guac nginx[5871]: nginx: [emerg] unknown “internatlfqdn” variable
    Dec 06 09:37:02 guac nginx[5871]: nginx: configuration file /etc/nginx/nginx.conf test failed

    I edited the file and I changed the ‘internalfqdn’ to the internal IP address of the machine and I was then able to restart NGINX.

    3. Now; here’s where I am stuck. I try running the next command and it fails…..

    Failed authorization procedure. (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from “Apache Tomcat/8.0.32 (Ubuntu) – Error reportH1 {font-family:Tah”

    – The following errors were reported by the server:

    Type: unauthorized
    Detail: Invalid response from
    “Apache Tomcat/8.0.32 (Ubuntu) –
    Error reportH1 {font-family:Tah”

    To fix these errors, please make sure that your domain name was
    entered correctly and the DNS A record(s) for that domain
    contain(s) the right IP address.

    I’ve tried so many things that I’ll not list them to see if you perhaps have a quick answer to my issue.



  2. Noel Noel

    Hey There-

    Just wanted to come back and tell you that I figured the issue out. ๐Ÿ™‚

    I had redirected port 80 to port 8080 on my FW to test Guacamole.

    I went and change the redirection to reflect port 80 going to port 80 and Viola!

    Thanks again for some awesome documentation!



  3. Chase,

    For whatever reason my version of the letsencrypt client does not have the –pre and –post-hook commands and for that matter the –quiet. I used this instead for my crontab entry:

    (service nginx stop && letsencrypt renew && service nginx start) 2>&1 >/dev/null

    Otherwise great post. Thanks.

  4. SlaMa SlaMa

    There’s typo preventing from proper working: internaTlfqdn instead of internalfqdn.
    After correction works like a charm.

  5. Scott Scott

    So this all works well except now at renewal time, it fails to renew. Guac still works because the cert has not expired. It’s not the script because the same thing happens if I manually stop nginx and issue the renew command. How did it issue the cert in the first place? Any insight would be appreciated. Thanks.

    All renewal attempts failed. The following certs could not be renewed:
    /etc/letsencrypt/live/ (failure)
    1 renew failure(s), 0 parse failure(s)

    – The following errors were reported by the server:

    Type: connection
    Detail: Could not connect to

    • Chase Chase

      I think what you need to do is stop NGINX, comment out the line return 301 https://$host$request_uri;, start NGINX, run the renewal. Then put it back…try it, let me know if that works for you.

        • Chase Chase

          I agree, I need to create a better automated process ๐Ÿ™‚ I don’t use this personally so I’ve never had motivation to fix it.

          • I was able to automate it without messing with the nginx config file. Just use something like this in your crontab entry:

            37 */12 * * * (service nginx stop && letsencrypt –standalone renew && service nginx start) 2>&1 >/dev/null

  6. I wanted to let you know that I have been working with users on the Guac forums and we isolated a problem that was a result of the Nginx config you have displayed here.

    To summarize…

    All of our connections were posting errors in the logs….
    11:10:59.020 [http-nio-8080-exec-57] INFO o.a.g.t.h.RestrictedGuacamoleHTTPTunnelServlet – Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.

    This was causing lag when a user would open more than 3 connections in multiple tabs in the web browser.

    One of the senior guys noticed that the Nginx config had this…
    proxy_set_header Upgrade $http_upgrade;

    He asked me why the “” before the variable. So I removed it and all logs about reduced performance was gone, and all lag in the browsers was gone.

    I just wanted to give you a heads up. Thanks again for your Guac info you post.

    • Chase Chase

      Hey that’s good to know! Thanks for pointing that out. My real config on my own device doesn’t have the backslash. I’m not sure how it got in there…

      EDIT: Actually it looks like you copied and pasted instead of running the script? In my script the backslash is before the $ because it’s being used in the bash shell as a literal string through “cat” to write to the file. So if you use the script it will print only $ but if you copy and paste you end up with the $

  7. thank you so much for a great resource.

    when I use Nginx with my Guacamole Server. I can no longer print to the Guacamole Printer. I get “Fail – Network Error” in chrome or any other browser.
    it opens the save to location, lets me enter a name, but then gets the failed – network error.

    any suggestions?

    • Lars Lars

      I had problems with printing to pdf and downloading files on 0.9.13 and tomcat8. After downgrading tomcat to version 7 it worked for me.

  8. Jeff R Jeff R


    I have been trying to renew my LE cert for Guac and have commented out the line:

    # return 301 https://$host$request_uri; in nginix.conf file

    and it still seems to do a redirect and the cert renewal is failing. Am I missing something?


  9. Lucky Lucky

    Hey Chase, could you please help me to extend the configuration to connect to a guacamole Server and an Exchange OWA Server with letsEncrypt and Nginx? Thanks

  10. John John

    I’m getting this message when I try to restart NGINX:

    nginx.service – A high performance web server and a reverse proxy server
    Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
    Active: failed (Result: exit-code) since Fri 2018-02-02 04:28:03 PST; 23s ago
    Process: 3816 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)

    Feb 02 04:28:03 guac01 systemd[1]: Starting A high performance web server and a reverse proxy server…
    Feb 02 04:28:03 guac01 nginx[3816]: nginx: [emerg] invalid number of arguments in “server_name” directive in /etc/nginx/ngi
    Feb 02 04:28:03 guac01 nginx[3816]: nginx: configuration file /etc/nginx/nginx.conf test failed
    Feb 02 04:28:03 guac01 systemd[1]: nginx.service: Control process exited, code=exited status=1
    Feb 02 04:28:03 guac01 systemd[1]: Failed to start A high performance web server and a reverse proxy server.
    Feb 02 04:28:03 guac01 systemd[1]: nginx.service: Unit entered failed state.
    Feb 02 04:28:03 guac01 systemd[1]: nginx.service: Failed with result ‘exit-code’.

    Any help?

    • Feb 02 04:28:03 guac01 nginx[3816]: nginx: [emerg] invalid number of arguments in โ€œserver_nameโ€ directive in /etc/nginx/ngi
      Feb 02 04:28:03 guac01 nginx[3816]: nginx: configuration file /etc/nginx/nginx.conf test failed

      I’d start there

  11. RR RR

    Having issues and dont know where to start. I copy/pasted but was careful on the formatting. Listed below is my nginx conf file.

    • #1. You copied and pasted the config instead of the entire command as written.

      #2. Run “nginx -t” and it will test the config file and tell you what lines are broken.

      Your primary issue is #1. Where you have escaped some of the variables (indicated by the use of the dollar sign, $)

      • RR RR

        ~$ sudo nginx -t
        nginx: [emerg] BIO_new_file(“/etc/nginx/ssl/$EXTERNALFQDN/fullchain.pem”) failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen(‘/etc/nginx/ssl/$EXTERNALFQDN/fullchain.pem’,’r’) error:2006D080:BIO routines:BIO_new_file:no such file)

        I set the variables for EXTERNALFQDN beforehand so should I just write it out explicity instead of relying on variables

        • You set them but when you copied and pasted you didn’t allow the terminal to expand them into whatever you set them to. Instead you’ve got a literal $VARIABLE. So yes, go edit your configuration file and point to the correct paths…

        • RR RR

          Lol. I see what you mean now by 1. Looks like I have some work to do. You can delete the prior reply. Thanks again

          • RR RR

            Thanks a million. I was able to get it sorted out. Last question where is the usermapping file that guacamole uses to store mysql database settings. I need to delete a user mapping ??

  12. brian mullan brian mullan

    The script for nginx has 2 variables that are undefined before they are used.

    In the section – Common Proxy Settings


    are not set before they are used so in the resultant /etc/nginx/nginx.conf both
    fail on proxy_set_header because they lack the 2nd argument

    I didn’t see anywhere in the post where it mentioned these 2 and I am wondering how
    people could have gotten this to work ?

    # Common Proxy Settings
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Leave a Reply

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