thefinn93/ansible-letsencrypt

letsencrypt get certificate port 80 conflict

witten opened this issue · 6 comments

letsencrypt cannot get the certificate if a web server or other process is already listening on port 80. Here's the ansible output of that situation:

TASK: [letsencrypt | Get the certificate] ************************************* 
failed: [example.com] => {"changed": true, "cmd": ["/usr/local/src/letsencrypt/env/bin/python", "/usr/local/src/letsencrypt/env/bin/letsencrypt", "--agree-dev-preview", "--agree-tos", "--text", "--domains", "example.com", "--email", "webmaster@example.com", "-a", "standalone", "auth"], "delta": "0:00:02.772124", "end": "2015-11-04 18:37:48.260437", "rc": 1, "start": "2015-11-04 18:37:45.488313"}
stderr: An unexpected error occurred.
EOFError: EOF when reading a line
Please see the logfiles in /var/log/letsencrypt for more details.
stdout: 
-------------------------------------------------------------------------------
The program nginx (process ID 2764) is already listening on TCP port 80. This
will prevent us from binding to that port. Please stop the nginx program
temporarily and then try again.
-------------------------------------------------------------------------------
Press Enter to ContinueIMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to webmaster@example.com.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.

FATAL: all hosts have already failed -- aborting

I'm not sure how your ansible role should deal with that, if at all. For instance, what I'll probably end up doing is stopping nginx before trying to get the certificate and start nginx after, but that's just because I happen to use nginx on this server.

Note that ansible handlers with "notify" won't work for this use case, because those run at the end and not during.

Here's what I ended up doing. This replaces the prior "Get the certificate" section:

  - stat: path="/etc/letsencrypt/live/{{ letsencrypt_cert_domain }}"
    register: cert_file

  - name: Stop nginx
    service: name=nginx state=stopped
    when: cert_file.stat.exists == False

  - name: Get the certificate
    command: "{{ letsencrypt_venv}}/bin/python {{ letsencrypt_venv }}/bin/letsencrypt --agree-dev-preview {% if letsencrypt_agree_tos %}--
    when: cert_file.stat.exists == False

  - name: Start nginx
    service: name=nginx state=started
    when: cert_file.stat.exists == False

This of course won't work for machines with Apache or other things listening on port 80.

I believe (although I haven't tested) that this is actually just an issue with the nginx authenticator module for LE. I've been meaning to test on a box with apache or something, but the goal of letsencrypt is zero-downtime certificate issuance

Figured out what to do about this: the webroot authenticator get's fed a path to the web server's document root and will authenticate without reconfiguring the web server or running it's own web server. Pushing changes to do this by default then fall back to the standalone authenticator in a bit.

See the webroot branch for deets. I haven't tested it much yet

I just tried your webroot branch, and it appears to work great when nginx is running:

TASK: [letsencrypt | Attempt to get the certificate using the webroot authenticator] *** 
changed: [example.com]

TASK: [letsencrypt | Attempt to get the certificate using the standalone authenticator (in case eg the webserver isn't running yet)] *** 
skipping: [example.com]
ok: [example.com]

Thanks for the update!

Awesome! Merged