Community site: login faq

What's the simplest way to issue and apply LetsEncrypt SSL certificates from my WebFaction server?

asked 23 May '16, 07:39

ryans's gravatar image

ryans ♦♦
accept rate: 43%

There are many ways to go about utilizing LetsEncrypt. Here's one designed for WebFaction specifically and built in ruby.

That said, I personally prefer a minimalist approach - one that works identically for all application types and relies on nothing but bash, netcat, and OpenSSL. So here I'll describe how I personally use LetsEncrypt, but feel free to use whatever you find most convenient.

Lastly, note that you're going to need to take your site offline for a short period of time (about 10 minutes) during this procedure, so you'll want to plan accordingly.

To begin, create a new application of type "Custom Application (Listening on Port)". I called mine "letsencrypt_validation". You'll be able to use this one application for all certificates now and in the future; you do not need to create a separate one for each of your sites.

Note the port assigned to this application -- In this guide, I'll use port "77777". Replace this with your port number anywhere it is used in the following instructions.

Next, ensure that you have both HTTP and HTTPS website records for your website in your Websites list. If you only have an HTTP site, then make a copy of it that is identical except for using HTTPS instead. At this point you should have two website records for your domain- one HTTP, and one HTTPS.

For the sake of this example, I'll name these website records "mysite" and "mysite_ssl" and have them both serve the domains "mydomain.com" and "www.mydomain.com". Similarly, replace these values with your website and domain names wherever they are used below.

Now, in an SSH command prompt, install acme.sh, a pure-shell implementation of the Automatic Certificate Management Environment ("ACME"):

mkdir -p $HOME/src
cd $HOME/src
git clone 'https://github.com/Neilpang/acme.sh.git'
cd ./acme.sh
./acme.sh --install

The "install" routine creates a directory at ~/.acme.sh/, which will hold the script as well as any certificates that you issue. Despite my disdain for "installing" things, I have to recommend using "./acme.sh --install" here; the resulting organization of certificates in ~/.acme.sh/ is worth it. It will also create a cron job to automatically check your certificates for renewal (inspect your crontab to see it). After doing this, log out and back in again.

Now you're ready to issue the certificates. Return to the Control Panel Websites list and edit your HTTP and HTTPS websites ("mysite" and "mysite_ssl") so that the new "letsencrypt_validation" custom application is mounted at the root of each. This will take your site offline. Refresh your site (using the http:// URL) until you see a "502 Bad Gateway" error -- that's what you want to see.

Now, from the command line, generate a test (staging) certificate as follows:

Le_HTTPPort=77777 acme.sh --test --issue -d mydomain.com -d www.mydomain.com --standalone

This instructs acme.sh to bind to port 77777 (the letsencrypt_validation custom application port) and in this way prove to the Certificate Authority that you control the domains for which you are issuing a certificate. If all goes well, you'll receive a message that the new certificate was successfully issued and is stored in "~/.acme.sh/mydomain.com".

That was just a test certificate -- you can't actually use it. But now that you know that certificate can be issued without errors, you're ready to issue it for real. Delete the directory containing the test certificate and proceed to issue the real one (by removing "--test" in the acme.sh command):

rm -r $HOME/.acme.sh/mydomain.com
Le_HTTPPort=77777 acme.sh --issue -d mydomain.com -d www.mydomain.com --standalone

You should achieve the same result as previously, except this time you'll have a real, working certificate, ready for installation.

Before proceeding with installation, return to your Websites list and switch your site back to your original application so that it's no longer serving the "letsencrypt_validation" custom app. This brings your site back online.

Finally, you can install your certificate via the Control Panel. Hope that helps!

permanent link
This answer is marked "community wiki".

answered 23 May '16, 08:30

ryans's gravatar image

ryans ♦♦
accept rate: 43%

edited 23 Oct '16, 03:30

This is a great way of doing it! I've linked to this from the Ruby tool's docs as an alternate approach.

As a note, you might be able to add "MAILTO="support@webfaction.com" MAILFROM="myaccountemail@example.com" to the beginning of this command and add " && echo "Please apply the cert in $SOMEWAYOFGETTINGHTEPATH to $LISTOFDOMAINS" to the end, and make this fully automated. Although you'd still have to figure out how to automate the process of switching the applications (which could be done from the API)…

When you get cert activation set up in the control panel, are you integrating LE specifically, or will an external tool still need to be used for that part? Is an API being created for this, so that an external tool could automate cert installation? Is it possible to beta test this feature?

(23 May '16, 11:16) williaminwi williaminwi's gravatar image

Certificate management will be for any type of SSL certificate, with some extra features for automating LE. API support will probably happen at some point, but I can't confirm that. Beta testing is not available.

(23 May '16, 15:04) seanf ♦♦ seanf's gravatar image

In this guide, I'll use port "77777". Replace this with your port number anywhere it is used in the following instructions.

I'm not good with servers, CLI, or encryption, so excuse my novice question... but how do I find/set my port number? Is it arbitrary?

(05 Jun '16, 20:15) danfo danfo's gravatar image

@danfo When you create a new application of type "Custom Application (Listening on Port)" as called for by Ryan's instruction the Control Panel will assign a port to you.

You can check https://my.webfaction.com/applications/ and select the application the reserved port is recorded in the port field.

(05 Jun '16, 20:20) aaront ♦♦ aaront's gravatar image

Thanks @aaront

I followed the instructions but after running Le_HTTPPort=77777 acme.sh --test --issue -d mydomain.com -d www.mydomain.com --standalone (with my values plugged in) I get an error Please stop it first

Any advice? Again... I'm server stupid but I Googled it and the error is this one, on line 1159 of https://github.com/Neilpang/acme.sh/blob/master/acme.sh

(05 Jun '16, 21:29) danfo danfo's gravatar image

We're going to need some more details (account information) to help you further. Please open a support ticket with those details and we can look further into this.

(05 Jun '16, 21:35) NickR ♦♦ NickR's gravatar image

This info is great. So, the cron initiates the certificate renewal automatically. Does this renewal process take my site offline for 10 minutes? Does it automatically switch my website to point to the 'letsencrypt_validation' app, or is that not necessary for the renewal?

(03 Oct '16, 15:08) Joe028 Joe028's gravatar image

Cron will notify you when it is time to do this again. Each time you edit the site record to update it it will take it offline. The post could be updated with the last step being in the control panel. You no longer need to send us support tickets to activate and manage SSL.

(03 Oct '16, 23:53) johns ♦♦ johns's gravatar image

Why do we create a new application "letsencrypt_validation" associating our website records with this app instead of using our regular website records (assuming we have created a HTTPS version) and of our existing application and do Le_HTTPPort=<port_of_existing_app> acme.sh --issue -d mydomain.com -d www.mydomain.com --standalone? Will this not work? Is it mandatory to have our site offline for a little?

(11 Dec '16, 20:24) nik nik's gravatar image

I just installed acme.sh and then I logged out and then logged in. After that I run acme.sh --test --debug --issue -d mydomain.com -d www.mydomain.com -w /home/username/webapps/my_app/ and all I got was an error during the verification of the domains. The error from acme.sh was: Diagnosis versions: openssl:openssl OpenSSL 1.0.1e-fips 11 Feb 2013. Am I doing something wrong?

(12 Dec '16, 09:37) nik nik's gravatar image

What I want is to avoid (if possible) to shutdown my website (even for a few minutes), bind it to another app, issue/renew a certificate, install it (via the API), bring my website alive again with my working app. I just want to avoid this procedure every 3 months.

(12 Dec '16, 10:56) nik nik's gravatar image


The reason for the use of a custom app listening on a port is explained in the OP.

"This instructs acme.sh to bind to port 77777 (the letsencrypt_validation custom application port) and in this way prove to the Certificate Authority that you control the domains for which you are issuing a certificate."

If you are having issues, please open a support ticket and we can take a look. Be sure to include as much detail as possible to assist us in troubleshooting your ticket.

(12 Dec '16, 23:05) NickR ♦♦ NickR's gravatar image
showing 5 of 12 show 7 more comments

OK. I found my own way embedding LetsEncrypt certifications by combining several answers from here and there. Many thanks to cpbotha, Neilpang acme.sh and ryans answer above. Forgive me if there is someone I forget.

I tried several tools (such as letsencrypt-webfaction by will-in-wi) but none of them came in handy for my Django powered website.

I already had hosted, in Webfaction, the HTTP version of my website and all I wanted was to redirect traffic to the HTTPS version. But in order to do that, I needed certificates. Below, follows the procedure I took.

  1. Login to your Applications and click Add new application
  2. Select a name for it, say my_ssl_app and under the App category menu select PHP. Under the App type select Static/CGI/PHP-7.0. Click the Save button.
  3. Now navigate to your Websites, make an exact copy of your existent HTTP website and enable HTTPS on it. How? Simple create a new website, choose the same domains (with and without www), choose the same Application (not the one we have just created) but do not forget to select the button Encrypted website (https). Click Save.
  4. You will notice that your HTTPS website says Security HTTPS, using shared certificate. That's OK for now. We'll fix that later.
  5. Select your HTTP version of your website and under the Contents section remove your existing application. Then, add the new one we just created (my_ssl_app). Click Save.
  6. Now, if you visit your site you will NOT get your usual homepage. We have not done any redirection to HTTPS yet. Stay with me!
  7. From your local machine open terminal and ssh yourUserName@webXXX.webfaction.com
  8. vim ~/webapps/my_ssl_app/.htaccess
  9. Hit key i (to enter Insert mode and start writing), copy (Ctrl + c) the following text and paste it (Ctrl + Shift + v) to the opened file (.htaccess). After pasting, hit the Esc key and then write :wq (this will save the file and quit the vim editor):

    RewriteEngine on

    RewriteRule !^.well-known($|/) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

  10. Done with redirection. Now if you visit your site (mysite.com) you will be redirected to https://mysite.com, BUT a security warning will arise saying that the site you are trying to visit may be dangerous or so. That's because we are using a shared certificate. Getting closer!

  11. In terminal you must install the terrific acme.sh">https://github.com/Neilpang/acme.sh)">acme.sh script. Simply curl https://get.acme.sh | sh

  12. Everything is done automatically for you (and me!). Log out from the terminal and ssh to log back in.

  13. Now you have the command acme.sh available globally. Time to use it.

  14. Before, of course, to request a brand new official certificate from LetsEncrypt, we must request a staging (test) certifiacte, in order to be sure that everything is working properly. So...

  15. acme.sh --issue --test -d mysite.com -d www.mysite.com -w ~/webapps/my_ssl_app

  16. If everything worked, you should have 7 files to the path ~/.acme.sh/mysite.com/ which are (ca.cer, fullchain.cer, mysite.com.cer, mysite.com.conf, mysite.com.csr, mysite.com.csr.conf and mysite.com.key). If something is missing, then maybe this is because these are just test certificates and keys. Not usable in production.

  17. Now that everything worked, it's time to issue for the real ones.

  18. Just enter acme.sh --issue -d mysite.com -d www.mysite.com -w ~/webapps/my_ssl_app

  19. The above command will fetch the same kind of files (with the same name) but this time this folks are official. Their lifetime is 90 days and LetEncrypt lets you renew your certificates no earlier than 60 days after your last issue. For example, if you issued your certificates today (2016-12-13) then the earlier you can issue them again (renew them) is at 2017-02-13. Of course there is always the option to renew them earlier by using the --force argument.

  20. Now go to the SSL certificates, select Add SSL Certificate and choose Upload Certificate. This step, you only have to do it once. Give it a name, say mysite_cert and then copy the contents of ~/.acme.sh/mysite.com/mysite.com.cer to a file and the upload it to the Certificate section. Do the same with the ~/.acme.sh/mysite.com.key and the Private Key section and finally with the ~/.acme.sh/ca.cer and the Intermediates/bundle section. All these could be done via the create_certificate funtion of the Webfaction's API, of course.

  21. Now for my favourite part, automation. I have written a Python (2.7) script in order to talk to Webfaction's API and update my certificates automatically without bringing my site offline AND without having me (a human) to interact with the Control Panel every 2 months in order to install manually the renewed certificates. This Python script is executed every day (as a cron job).

    #!/usr/local/bin python
    from os import getcwd, chdir, listdir
    from sys import exit
    from subprocess import Popen, PIPE
    from xmlrpclib import ServerProxy, Fault
    if __name__ == '__main__':
        # Run the command advised by acme.sh script in order to renew the certificates.
        # Each certificate lasts 90 days and the max permitted day to renew a certificate is 60 days from the issue date -
        # in other words the earlier we can renew a certificate is 30 days before expiration. This can be changed through
        # the --days argument during the --issue step. Type ".acme.sh/acme.sh --help" for more information.
        # This script will run as a cron job every day in order for the certs to be renewed when appropriate.
        acme_process = Popen(['%s/acme.sh' % HIDDEN_ACME_DIR_NAME, 'cron'], stdout=PIPE, stderr=PIPE)
        out, err = acme_process.communicate()
        if err:
            exit("An error occurred during the renewal process. Error: {}".format(err))
        if 'Cert success.' in out:
            d = {
                'url': 'https://api.webfaction.com/',  # Fixed. Not to be changed.
                 'version': 2,  # Fixed. Not to be changed.
                 's_name': 'WebXXX',  # Your Web server's name.
                 'user': 'username',  # Your Webfaction username.
                 'pwd': 'password',  # Your Webfaction password.
                 'domain': 'mysite.com',  # Your domain name where you issued the certificate.
                 'cert_name': 'mysite_cert',  # Your certification name (see step #20).
            # Initially empty values (to be filled later with data from files)
            domain_cert, pv_key, intermediate_cert = '', '', ''
            # Directory declarations in order to know where to work
            valid_cert_dir = '/home/{user}/{acme}/{domain}'.format(user=d.get('user'), acme=HIDDEN_ACME_DIR_NAME, domain=d.get('domain'))
            # Change directory to the one that matches our domain
            # Test if current working directory is the valid one
                assert getcwd() == valid_cert_dir
            except AssertionError:
                exit('Current working directory is not {}! Instead is {}.'.format(valid_cert_dir, getcwd()))
            # try to connect to Webfaction API
                server = ServerProxy(d.get('url'))
                session_id, _ = server.login(d.get('user'), d.get('pwd'), d.get('s_name'), d.get('version'))
            except Fault as e:
                exit("Exception occurred at connection with Webfaction's API. {}".format(e))
                # Connection is successful. Proceed...
                # read domain certificate and store it as a variable
                file_to_read = '{}.cer'.format(d.get('domain'))
                    assert file_to_read in listdir('.')
                except AssertionError:
                    exit('The file \"{}\" does not exist inside \"{}\".'.format(file_to_read, getcwd()))
                    with open(file_to_read, 'r') as f:
                        domain_cert = f.read()
                # read private key certificate and store it as a variable
                file_to_read = '{}.key'.format(d.get('domain'))
                    assert file_to_read in listdir('.')
                except AssertionError:
                    exit('The file \"{}\" does not exist inside \"{}\".'.format(file_to_read, getcwd()))
                    with open(file_to_read, 'r') as f:
                        pv_key = f.read()
                # read intermediate certificate and store it as a variable
                file_to_read = 'ca.cer'
                    assert file_to_read in listdir('.')
                except AssertionError:
                    exit('The file \"{}\" does not exist inside \"{}\".'.format(file_to_read, getcwd()))
                    with open(file_to_read, 'r') as f:
                        intermediate_cert = f.read()
                # Install the renewed certificate to your Web server through the Webfaction's API
                if domain_cert and pv_key and intermediate_cert:
                # https://docs.webfaction.com/xmlrpc-api/apiref.html#method-update_certificate
                # update_certificate(session_id, name, certificate, private_key, intermediates)
                server.update_certificate(session_id, d.get('cert_name'), domain_cert, pv_key, intermediate_cert)
  22. Save the above to your server, say as .certificate_renewal.py and place it somewhere, say at ~/webapps/your_app/.certificate_renewal.py.

  23. Now, crontab -e and delete the line at the very bottom that was inserted during the installation of the acme.sh script before (step #10).

  24. Instead, write 0 2 * * * /usr/local/bin/python $HOME/webapps/your_app/.certificate_renewal.py 2>> /path/to/your/log

  25. The above cron job will run every day at 02.00 (am) and check if your certificates need any renewal. If so, then they will be automatically updated for you (via the function update_certificate) from the API. GREAT!!!

  26. Last step. Go to your websites and choose the HTTPS version of your domain. Under the "Security" section, "Choose a certificate" dropdown menu, choose the certificate you created (not the "Shared certificate", of course). You will find it with the name you gave it on step 20 (in this example we gave it the name mysite_cert.

Hope this (tutorial) helps someone. You can see it here too Webfaction Letsencrypt Django.


permanent link

answered 13 Dec '16, 19:27

nik's gravatar image

accept rate: 16%

edited 16 Jan, 19:52

thanks for step by step instructions, I successfully installed Let's Encrypt for my Django app

(23 Dec '16, 00:33) zdenulo zdenulo's gravatar image

Glad this guide helped you! If you found a "bug" in this guide don't hesitate to post here in order to check it out and update the guide, accordingly.

(24 Dec '16, 13:02) nik nik's gravatar image

Thank you so much for this!!! Finally a complete guide that just works! A couple of notes: The indentation on the .certificate_renewal.py is a little off. I would also perhaps suggest putting all the variables such as username, server, etc. at the beginning, so it's easier to see what needs to be modified. There's also an option to copy/paste the certificate, which saves the step of creating/copying the files down and then uploading them – I found that to be quite handy. And lastly, you might want to add one step: 26. Go to your website panel and edit your https site to use the new certificate. These are all minor improvements of course. Thanks again – huge help!

(29 Dec '16, 18:09) febo febo's gravatar image

Thank you! helped me a lot. I will try the script to autorenew later

(30 Dec '16, 12:40) yabirgb yabirgb's gravatar image

Actually, performing steps 1-9 and then using letsencrypt-webfaction makes this even easier and works for Django apps.

(30 Dec '16, 16:56) febo febo's gravatar image

Thank you all for your comments! @febo: I will look at this "there's also an option to copy/paste the certificate, which saves the step of creating/copying the files down and then uploading them"! Forgot about step 26. You are right! Will add it!

(30 Dec '16, 17:23) nik nik's gravatar image
showing 5 of 6 show 1 more comments

I have 30+ Let's encrypt certificates for my private projects, and renewing them manually might invite many errors. How could your renewal script be extended with a hard-coded array, into which we can paste the webfaction certificate names? Thank you very much nik!

permanent link

answered 03 Feb, 05:23

xp3-dot-us's gravatar image

accept rate: 0%

I think that is easy.

Wrap the above script into a function (say def renew_cert(domain): ...) which will take a string as a parameter (domain, e.g 'mywebsite.com').

After the function declaration define a list with the hard-coded project names (better make it domain names) and then loop in each of them and call the function.

If you want to make it more automated, be sure to follow a common pattern when naming your certificates. If your domain is named 'mywebsite.com' then name your cert as 'mywebsite_cert'. So, with a simple pythonic string substitution, the dictionary value of cert_name could become dynamically generated from the domain parameter.


def renew_cert(domain):
    # ...
    d = {
        'domain': domain,
        'cert_name': '{}_cert'.format(domain.split('.')[-2])


if __name__ == '__main__':
    my_domains = ['mysite1.com', 'mysite2.com', 'mysite3.com', ...]

    for domain in my_domains:
(03 Feb, 14:56) nik nik's gravatar image

Thank you nik! My Python skills are close to zero. During testing, how can I replace renew_cert(domain) either to write to a file or to send me an email?

(03 Feb, 15:43) xp3-dot-us xp3-dot-us's gravatar image

Hello again. You don't have to worry about your zero python skills, although if you want to extend this knowledge you should start learning python (and more particular python 3, not 2). Here's an excellent course that got me started with python 4 years ago (MITx - Introduction to Computer Science and Programming Using Python). I strongly suggest to take this course if you have the time of course. Professors are excellent and you will gain a lot from this. This course, once it was first introduced, its being repeating 2 or 3 times per year (with ammendements in each "version").

As for your question, there are multiple ways to write to a file or send email: either from inside the python script or (better) from the terminal.

Here's how:

Write to a file

This assumes that you enter some print statements inside the script in order for these prints to be written to the file. For example, enter a print 'Writting data to %s' % file_to_read before each with open(...) clause and one after that (print 'Finished %s' % file_to_read). After that, run the script: python .certificate_renewal.py > $HOME/tmp/data.txt (assuming that the tmp dir exists, of course). This will redirect all print statements into the file data.txt. Careful though, > will clear previous data each time you execute the script. If you want use >>. This will append current data to the previous ones.

Sending an email

Nothing fancy here too. Assuming that you have set your print statements, using the linux terminal its as easy as:

mail -s "Subject here" your@email.com < python .certificate_renewal.py

This is just a linux command. There are others too, sendmail, mutt etc.

(04 Feb, 06:42) nik nik's gravatar image

Code is poetry (-;

(04 Feb, 16:09) xp3-dot-us xp3-dot-us's gravatar image

Nik, I signed up for the Python course and installed Anaconda, which looks good. In a way I am getting too old for this fiddling, but I will stick with it a little longer. Maybe one day you could improve the script to include multiple domains (auto populated from the acme.sh folder)? holger@xp3.us

(05 Feb, 04:44) xp3-dot-us xp3-dot-us's gravatar image
Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here



Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "title")
  • image?![alt text](/path/img.jpg "title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Question tags:


question asked: 23 May '16, 07:39

question was seen: 4,300 times

last updated: 05 Feb, 04:44