Repository: certbot/certbot

Issue:
nginx authenticator should also modify https virtualhosts, to avoid breaking under secured CDN->Origin traffic
## My operating system is (include version):
Ubuntu Disco

## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
certbot-auto (0.36.0)

## I ran this command and it produced this output:
    certbot certonly -a nginx -d example.com --dry-run --debug-challenges

## Certbot's behavior differed from what I expected because:
My domain is hypothetically behind Cloudflare and setup with [Full (Strict)](https://support.cloudflare.com/hc/en-us/articles/200170416-End-to-end-HTTPS-with-Cloudflare-Part-3-SSL-options#h_8afd8a8d-382d-4694-a2b2-44cbc9f637ef) mode enabled. This means that all requests from Cloudflare to my server arrive over HTTPS.

However, Certbot's nginx authenticator only modifies the port 80 virtualhost to setup the HTTP-01 challenge response.

Here is the config before `--debug-challenges` pauses:

```nginx
server {
        listen 80;
        listen [::]:80;
        server_name example.com;
        root /var/www/example.com;
        index index.html;
        return 302 https://example.com$request_uri;
}
server {
        listen 443;
        listen [::]:443;
        server_name example.com;
        ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
        ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
        root /var/www/example.com;
        index index.html;
        return 302 https://example.com$request_uri;
}
```

Here it is during the `--debug-challenges` pause where it has only modified the port 80 virtualhost:

```nginx
server {rewrite ^(/.well-known/acme-challenge/.*) $1 break; # managed by Certbot


        listen 80;
        listen [::]:80;
        server_name example.com;
        root /var/www/example.com;
        index index.html;
        return 302 https://example.com$request_uri;
location = /.well-known/acme-challenge/myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk{default_type text/plain;return 200 myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk.OcK5xDoU-5hzVOSoebZpn47qyw2WhBotKjK3oN_1gIs;} # managed by Certbot

}
server {
        listen 443;
        listen [::]:443;
        server_name example.com;
        ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
        ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
        root /var/www/example.com;
        index index.html;
        return 302 https://example.com$request_uri;
}
```

If as described, the domain is behind Cloudflare CDN is strictly talking to the origin over HTTPS, the challenge response location won't apply to the request and the challenge will fail:

    IMPORTANT NOTES:
    - The following errors were reported by the server:

    Domain: example.com
    Type:   unauthorized
    Detail: Invalid response from http://example.com/.well-known/acme-challenge/...

## Here is a Certbot log showing the issue (if available):
###### Logs are stored in `/var/log/letsencrypt` by default. Feel free to redact domains, e-mail and IP addresses as you see fit.

<details>
<summary>Example Log</summary>

    2019-07-28 19:49:08,230:DEBUG:certbot.main:certbot version: 0.36.0
    2019-07-28 19:49:08,231:DEBUG:certbot.main:Arguments: ['-a', 'nginx', '-d', 'foo.monkas.xyz', '--dry-run', '--debug-challenges', '--logs-dir', '/tmp/logsdir', '--config-dir', '/tmp/confdir', '--work-dir', '/tmp/wrkdir', '--register-unsafely-without-email']
    2019-07-28 19:49:08,231:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#apache,PluginEntryPoint#manual,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
    2019-07-28 19:49:08,240:DEBUG:certbot.log:Root logging level set at 20
    2019-07-28 19:49:08,240:INFO:certbot.log:Saving debug log to /tmp/logsdir/letsencrypt.log
    2019-07-28 19:49:08,241:DEBUG:certbot.plugins.selection:Requested authenticator nginx and installer None
    2019-07-28 19:49:08,328:DEBUG:certbot.plugins.selection:Single candidate plugin: * nginx
    Description: Nginx Web Server plugin
    Interfaces: IAuthenticator, IInstaller, IPlugin
    Entry point: nginx = certbot_nginx.configurator:NginxConfigurator
    Initialized: <certbot_nginx.configurator.NginxConfigurator object at 0x7fa796429fd0>
    Prep: True
    2019-07-28 19:49:08,329:DEBUG:certbot.plugins.selection:Selected authenticator <certbot_nginx.configurator.NginxConfigurator object at 0x7fa796429fd0> and installer None
    2019-07-28 19:49:08,329:INFO:certbot.plugins.selection:Plugins selected: Authenticator nginx, Installer None
    2019-07-28 19:49:08,332:DEBUG:certbot.main:Picked account: <Account(RegistrationResource(body=Registration(status=None, terms_of_service_agreed=None, agreement=None, only_return_existing=None, contact=(), key=None, external_account_binding=None), uri=u'https://acme-staging-v02.api.letsencrypt.org/acme/acct/10306454', new_authzr_uri=None, terms_of_service=None), f83c5f58684c73354ec763800882cc7b, Meta(creation_host=u'6f0a1410.ngrok.io', creation_dt=datetime.datetime(2019, 7, 28, 9, 46, 20, tzinfo=<UTC>)))>
    2019-07-28 19:49:08,333:DEBUG:acme.client:Sending GET request to https://acme-staging-v02.api.letsencrypt.org/directory.
    2019-07-28 19:49:08,334:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): acme-staging-v02.api.letsencrypt.org:443
    2019-07-28 19:49:08,696:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "GET /directory HTTP/1.1" 200 724
    2019-07-28 19:49:08,698:DEBUG:acme.client:Received response:
    HTTP 200
    Server: nginx
    Content-Type: application/json
    Content-Length: 724
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Expires: Sun, 28 Jul 2019 09:49:08 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:08 GMT
    Connection: keep-alive

    {
    "22l-B9FRhgU": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
    "keyChange": "https://acme-staging-v02.api.letsencrypt.org/acme/key-change",
    "meta": {
        "caaIdentities": [
        "letsencrypt.org"
        ],
        "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
        "website": "https://letsencrypt.org/docs/staging-environment/"
    },
    "newAccount": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
    "newNonce": "https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce",
    "newOrder": "https://acme-staging-v02.api.letsencrypt.org/acme/new-order",
    "revokeCert": "https://acme-staging-v02.api.letsencrypt.org/acme/revoke-cert"
    }
    2019-07-28 19:49:08,698:INFO:certbot.main:Obtaining a new certificate
    2019-07-28 19:49:08,767:DEBUG:acme.client:Requesting fresh nonce
    2019-07-28 19:49:08,767:DEBUG:acme.client:Sending HEAD request to https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce.
    2019-07-28 19:49:09,004:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "HEAD /acme/new-nonce HTTP/1.1" 200 0
    2019-07-28 19:49:09,008:DEBUG:acme.client:Received response:
    HTTP 200
    Server: nginx
    Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"
    Replay-Nonce: u19b9onk6LY_Sa_EqMKRG9Tfh3d2Jr-7LzeLzuZXa8E
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Content-Length: 0
    Expires: Sun, 28 Jul 2019 09:49:09 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:09 GMT
    Connection: keep-alive


    2019-07-28 19:49:09,009:DEBUG:acme.client:Storing nonce: u19b9onk6LY_Sa_EqMKRG9Tfh3d2Jr-7LzeLzuZXa8E
    2019-07-28 19:49:09,009:DEBUG:acme.client:JWS payload:
    {
    "identifiers": [
        {
        "type": "dns", 
        "value": "foo.monkas.xyz"
        }
    ]
    }
    2019-07-28 19:49:09,014:DEBUG:acme.client:Sending POST request to https://acme-staging-v02.api.letsencrypt.org/acme/new-order:
    {
    "protected": "eyJub25jZSI6ICJ1MTliOW9uazZMWV9TYV9FcU1LUkc5VGZoM2QySnItN0x6ZUx6dVpYYThFIiwgInVybCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL25ldy1vcmRlciIsICJraWQiOiAiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hY2N0LzEwMzA2NDU0IiwgImFsZyI6ICJSUzI1NiJ9", 
    "payload": "ewogICJpZGVudGlmaWVycyI6IFsKICAgIHsKICAgICAgInR5cGUiOiAiZG5zIiwgCiAgICAgICJ2YWx1ZSI6ICJmb28ubW9ua2FzLnh5eiIKICAgIH0KICBdCn0", 
    "signature": "np5dyooxz-tr9NpQiqgYU5XCR231MhD5Gq5-WWm9B__RcUqMxCkmgXjjS7ZJTxpM0z2fZlZh0MV2QGfeGpOl908U7eoaBtqCZZzh5YKVx0-Ggich5RUGVRlh4Mz4dv11CGndt97ZLfifukr1arE3cy_pgTyUc-bGEaogi9Q0gb-4fKLG2CR5sshTiw5U8eXegO2vAdXuoLXWQUIHSuBDeP_bdJB-LzmLJUC2g-yV_CjI_8FWyO0wXYpvMdp7OwesiIRV20OGKb_Tn8UQlXkzvUBG8QvIPX9Quxq38ua9Vy9DQYtpnc4XLyBWfIH999Keew2Eh6GGdMFAeR5PFmzqAA"
    }
    2019-07-28 19:49:09,311:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "POST /acme/new-order HTTP/1.1" 201 345
    2019-07-28 19:49:09,313:DEBUG:acme.client:Received response:
    HTTP 201
    Server: nginx
    Content-Type: application/json
    Content-Length: 345
    Boulder-Requester: 10306454
    Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"
    Location: https://acme-staging-v02.api.letsencrypt.org/acme/order/10306454/42762851
    Replay-Nonce: W7pN52NBQF6tQmpvZPlljrA-7DakA0GyGiIn3vW-GYg
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Expires: Sun, 28 Jul 2019 09:49:09 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:09 GMT
    Connection: keep-alive

    {
    "status": "pending",
    "expires": "2019-08-04T09:46:20Z",
    "identifiers": [
        {
        "type": "dns",
        "value": "foo.monkas.xyz"
        }
    ],
    "authorizations": [
        "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/1662827"
    ],
    "finalize": "https://acme-staging-v02.api.letsencrypt.org/acme/finalize/10306454/42762851"
    }
    2019-07-28 19:49:09,313:DEBUG:acme.client:Storing nonce: W7pN52NBQF6tQmpvZPlljrA-7DakA0GyGiIn3vW-GYg
    2019-07-28 19:49:09,314:DEBUG:acme.client:JWS payload:

    2019-07-28 19:49:09,316:DEBUG:acme.client:Sending POST request to https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/1662827:
    {
    "protected": "eyJub25jZSI6ICJXN3BONTJOQlFGNnRRbXB2WlBsbGpyQS03RGFrQTBHeUdpSW4zdlctR1lnIiwgInVybCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2F1dGh6LXYzLzE2NjI4MjciLCAia2lkIjogImh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC8xMDMwNjQ1NCIsICJhbGciOiAiUlMyNTYifQ", 
    "payload": "", 
    "signature": "trRjJUQ_7Cg8y-bxwb-5tlqsART3-z90wCi3ZdVOUE26uHUlMvGQ0x3dYtOTNrTtGB0CeRVkA_keMgxmQFgrKOU-fS0Ji6SCLvT8iUFflpfgwuWEyfgtkq11gcb_OmKembWzbZ5m5w7Sh8zYUJOVhYwoUwrfVwUg1_jFs5Eh_gML8cledVmCqviuakm7tWjVUq2T2277_cW9qIrbpvGsoBHKUXgXM5WLfRXYza5x5hJ9OFaRqy0_3ZRHKCCdz5bJlRN67fCMjinbQvDQnJB6w9eQajQ21JQ5rRzLQdicWlZprYlUQ7rtukO14V8zOXuhj_YpQGC7W7jqp6bQTmNlew"
    }
    2019-07-28 19:49:09,619:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "POST /acme/authz-v3/1662827 HTTP/1.1" 200 807
    2019-07-28 19:49:09,621:DEBUG:acme.client:Received response:
    HTTP 200
    Server: nginx
    Content-Type: application/json
    Content-Length: 807
    Boulder-Requester: 10306454
    Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"
    Replay-Nonce: Y_1eQVuh6cQhbQK5X-gxMcNJB8afO6_I40Y5TlRKDzU
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Expires: Sun, 28 Jul 2019 09:49:09 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:09 GMT
    Connection: keep-alive

    {
    "identifier": {
        "type": "dns",
        "value": "foo.monkas.xyz"
    },
    "status": "pending",
    "expires": "2019-08-04T09:46:20Z",
    "challenges": [
        {
        "type": "http-01",
        "status": "pending",
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9S6PhQ",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
        },
        {
        "type": "dns-01",
        "status": "pending",
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9jIS1Q",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
        },
        {
        "type": "tls-alpn-01",
        "status": "pending",
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/1jqvUg",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
        }
    ]
    }
    2019-07-28 19:49:09,622:DEBUG:acme.client:Storing nonce: Y_1eQVuh6cQhbQK5X-gxMcNJB8afO6_I40Y5TlRKDzU
    2019-07-28 19:49:09,624:INFO:certbot.auth_handler:Performing the following challenges:
    2019-07-28 19:49:09,624:INFO:certbot.auth_handler:http-01 challenge for foo.monkas.xyz
    2019-07-28 19:49:09,654:DEBUG:certbot_nginx.http_01:Generated server block:
    []
    2019-07-28 19:49:09,655:DEBUG:certbot.reverter:Creating backup of /etc/nginx/modules-enabled/50-mod-mail.conf
    2019-07-28 19:49:09,655:DEBUG:certbot.reverter:Creating backup of /etc/nginx/modules-enabled/50-mod-http-xslt-filter.conf
    2019-07-28 19:49:09,656:DEBUG:certbot.reverter:Creating backup of /etc/nginx/modules-enabled/50-mod-http-geoip.conf
    2019-07-28 19:49:09,656:DEBUG:certbot.reverter:Creating backup of /etc/nginx/modules-enabled/50-mod-http-image-filter.conf
    2019-07-28 19:49:09,657:DEBUG:certbot.reverter:Creating backup of /etc/nginx/modules-enabled/50-mod-stream.conf
    2019-07-28 19:49:09,657:DEBUG:certbot.reverter:Creating backup of /etc/nginx/mime.types
    2019-07-28 19:49:09,657:DEBUG:certbot.reverter:Creating backup of /etc/nginx/nginx.conf
    2019-07-28 19:49:09,657:DEBUG:certbot.reverter:Creating backup of /etc/nginx/sites-enabled/default
    2019-07-28 19:49:09,659:DEBUG:certbot_nginx.parser:Writing nginx conf tree to /etc/nginx/sites-enabled/default:
    ##
    # You should look at the following URL's in order to grasp a solid understanding
    # of Nginx configuration files in order to fully unleash the power of Nginx.
    # https://www.nginx.com/resources/wiki/start/
    # https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
    # https://wiki.debian.org/Nginx/DirectoryStructure
    #
    # In most cases, administrators will remove this file from sites-enabled/ and
    # leave it as reference inside of sites-available where it will continue to be
    # updated by the nginx packaging team.
    #
    # This file will automatically load configuration files provided by other
    # applications, such as Drupal or Wordpress. These applications will be made
    # available underneath a path with that package name, such as /drupal8.
    #
    # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
    ##

    # Default server configuration
    #
    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
            # First attempt to serve request as file, then
            # as directory, then fall back to displaying a 404.
            try_files $uri $uri/ =404;
        }

        # pass PHP scripts to FastCGI server
        #
        #location ~ \.php$ {
        #	include snippets/fastcgi-php.conf;
        #
        #	# With php-fpm (or other unix sockets):
        #	fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        #	# With php-cgi (or other tcp sockets):
        #	fastcgi_pass 127.0.0.1:9000;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #	deny all;
        #}
    }


    # Virtual Host configuration for example.com
    #
    # You can move that to a different file under sites-available/ and symlink that
    # to sites-enabled/ to enable it.
    #
    server {rewrite ^(/.well-known/acme-challenge/.*) $1 break; # managed by Certbot


        listen 80;
        listen [::]:80;
        server_name foo.monkas.xyz;
        root /var/www/example.com;
        index index.html;
        return 302 https://foo.monkas.xyz$request_uri;
    location = /.well-known/acme-challenge/myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk{default_type text/plain;return 200 myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk.OcK5xDoU-5hzVOSoebZpn47qyw2WhBotKjK3oN_1gIs;} # managed by Certbot

    }
    server {
        listen 443;
        listen [::]:443;
        server_name foo.monkas.xyz;
        ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
        ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
        root /var/www/example.com;
        index index.html;
        return 302 https://foo.monkas.xyz$request_uri;
    }

    2019-07-28 19:49:09,661:DEBUG:certbot_nginx.parser:Writing nginx conf tree to /etc/nginx/nginx.conf:
    user alex;
    worker_processes auto;
    pid /home/alex/nginx.pid;
    include /etc/nginx/modules-enabled/*.conf;

    events {
        worker_connections 768;
        # multi_accept on;
    }

    http {
    include /tmp/confdir/le_http_01_cert_challenge.conf;
    server_names_hash_bucket_size 128;

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /home/alex/nginx/access.log;
        error_log /home/alex/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }


    #mail {
    #	# See sample authentication script at:
    #	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
    # 
    #	# auth_http localhost/auth.php;
    #	# pop3_capabilities "TOP" "USER";
    #	# imap_capabilities "IMAP4rev1" "UIDPLUS";
    # 
    #	server {
    #		listen     localhost:110;
    #		protocol   pop3;
    #		proxy      on;
    #	}
    # 
    #	server {
    #		listen     localhost:143;
    #		protocol   imap;
    #		proxy      on;
    #	}
    #}

    2019-07-28 19:49:10,676:INFO:certbot.auth_handler:Waiting for verification...
    2019-07-28 19:49:12,633:DEBUG:acme.client:JWS payload:
    {
    "type": "http-01", 
    "resource": "challenge"
    }
    2019-07-28 19:49:12,637:DEBUG:acme.client:Sending POST request to https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9S6PhQ:
    {
    "protected": "eyJub25jZSI6ICJZXzFlUVZ1aDZjUWhiUUs1WC1neE1jTkpCOGFmTzZfSTQwWTVUbFJLRHpVIiwgInVybCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2NoYWxsLXYzLzE2NjI4MjcvOVM2UGhRIiwgImtpZCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTAzMDY0NTQiLCAiYWxnIjogIlJTMjU2In0", 
    "payload": "ewogICJ0eXBlIjogImh0dHAtMDEiLCAKICAicmVzb3VyY2UiOiAiY2hhbGxlbmdlIgp9", 
    "signature": "OCf2_mmDm4T0wzs_fWu87vDMtybAa8T7hCTRiKAmfx8SxMkaI6Z6bdyilzVqPGpVnLk0oK11_YX6CLq1BIG2sFEAGFwIMFt-waYHhh0vwQdxwORleASlhdfMGdVv8jZXRyTDqxDBP6ngG4DZZBj0DZrstDf5Ww6XRPwTCP_FdZUitU7NaGildZ2Sad7Y-WKgQuwfFBSlzZn9PyaU3dIWOp1OBapvlrn9nfYivgEtAWtYc5N8W1efgr3cJSePdOuvv6G5CNIZCN711rJKvdCl8-IRRYlQaV1aW5Pai6FjFTVr6usMqb2SBvUDcCk7ezhxS9EpTmHLIb0UcNt-HiB5xw"
    }
    2019-07-28 19:49:12,896:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "POST /acme/chall-v3/1662827/9S6PhQ HTTP/1.1" 200 190
    2019-07-28 19:49:12,897:DEBUG:acme.client:Received response:
    HTTP 200
    Server: nginx
    Content-Type: application/json
    Content-Length: 190
    Boulder-Requester: 10306454
    Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index", <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/1662827>;rel="up"
    Location: https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9S6PhQ
    Replay-Nonce: osty0Agba7ZpttWLGAjuEocML7JHpNcy1b9fo0KSY3Y
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Expires: Sun, 28 Jul 2019 09:49:12 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:12 GMT
    Connection: keep-alive

    {
    "type": "http-01",
    "status": "pending",
    "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9S6PhQ",
    "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
    }
    2019-07-28 19:49:12,898:DEBUG:acme.client:Storing nonce: osty0Agba7ZpttWLGAjuEocML7JHpNcy1b9fo0KSY3Y
    2019-07-28 19:49:13,901:DEBUG:acme.client:JWS payload:

    2019-07-28 19:49:13,905:DEBUG:acme.client:Sending POST request to https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/1662827:
    {
    "protected": "eyJub25jZSI6ICJvc3R5MEFnYmE3WnB0dFdMR0FqdUVvY01MN0pIcE5jeTFiOWZvMEtTWTNZIiwgInVybCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2F1dGh6LXYzLzE2NjI4MjciLCAia2lkIjogImh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC8xMDMwNjQ1NCIsICJhbGciOiAiUlMyNTYifQ", 
    "payload": "", 
    "signature": "tzBIT4jlPLkeedhKAWbcxM-uODoOaqY8rMEdDuJlQarWWOhZc04NNYhxL6JvnsmDWqrcZ2JHPu115CKEFR8kaEgbYoJjJbIuPrTN-QJ18LzL2p8xBUM-a8A5RZ4jStiSOEx4clnyfj-M2Hsbr7HCc_osKePfaSbkDZJYNR1n-9KYXbhErH4Q9sBE7ZyEQEGL10g1lRcSa-flVDhoRmFwmDJmCFvTrvKQcyQsLZtgNEagrSW5zsyQfRHRzIMb3p1vNJc91RypT50OvwcTrS-4ueqYuL5L2nc6IuMF01NBt7HJqPkwC5H30_QW8GmTjI4D-oB-W3fFyT2VNeq6jrrKww"
    }
    2019-07-28 19:49:14,227:DEBUG:urllib3.connectionpool:https://acme-staging-v02.api.letsencrypt.org:443 "POST /acme/authz-v3/1662827 HTTP/1.1" 200 1663
    2019-07-28 19:49:14,229:DEBUG:acme.client:Received response:
    HTTP 200
    Server: nginx
    Content-Type: application/json
    Content-Length: 1663
    Boulder-Requester: 10306454
    Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"
    Replay-Nonce: ydGna3Gwy1mmrkoPHBBvaX_wuGXt7vyZGhpVngyki90
    X-Frame-Options: DENY
    Strict-Transport-Security: max-age=604800
    Expires: Sun, 28 Jul 2019 09:49:14 GMT
    Cache-Control: max-age=0, no-cache, no-store
    Pragma: no-cache
    Date: Sun, 28 Jul 2019 09:49:14 GMT
    Connection: keep-alive

    {
    "identifier": {
        "type": "dns",
        "value": "foo.monkas.xyz"
    },
    "status": "invalid",
    "expires": "2019-08-04T09:46:20Z",
    "challenges": [
        {
        "type": "http-01",
        "status": "invalid",
        "error": {
            "type": "urn:ietf:params:acme:error:unauthorized",
            "detail": "Invalid response from http://foo.monkas.xyz/.well-known/acme-challenge/myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk [149.56.101.173]: \"\u003c!DOCTYPE HTML PUBLIC \\\"-//IETF//DTD HTML 2.0//EN\\\"\u003e\\n\u003chtml\u003e\u003chead\u003e\\n\u003ctitle\u003e404 Not Found\u003c/title\u003e\\n\u003c/head\u003e\u003cbody\u003e\\n\u003ch1\u003eNot Found\u003c/h1\u003e\\n\u003cp\"",
            "status": 403
        },
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9S6PhQ",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk",
        "validationRecord": [
            {
            "url": "http://foo.monkas.xyz/.well-known/acme-challenge/myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk",
            "hostname": "foo.monkas.xyz",
            "port": "80",
            "addressesResolved": [
                "149.56.101.173"
            ],
            "addressUsed": "149.56.101.173"
            }
        ]
        },
        {
        "type": "dns-01",
        "status": "invalid",
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/9jIS1Q",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
        },
        {
        "type": "tls-alpn-01",
        "status": "invalid",
        "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1662827/1jqvUg",
        "token": "myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk"
        }
    ]
    }
    2019-07-28 19:49:14,230:DEBUG:acme.client:Storing nonce: ydGna3Gwy1mmrkoPHBBvaX_wuGXt7vyZGhpVngyki90
    2019-07-28 19:49:14,231:WARNING:certbot.auth_handler:Challenge failed for domain foo.monkas.xyz
    2019-07-28 19:49:14,232:INFO:certbot.auth_handler:http-01 challenge for foo.monkas.xyz
    2019-07-28 19:49:14,233:DEBUG:certbot.reporter:Reporting to user: The following errors were reported by the server:

    Domain: foo.monkas.xyz
    Type:   unauthorized
    Detail: Invalid response from http://foo.monkas.xyz/.well-known/acme-challenge/myjYNbvYbrf4UGCwGvqtdpzDNEhzMkqTed3otSuAbQk [149.56.101.173]: "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not Found</h1>\n<p"

    To fix these errors, please make sure that your domain name was entered correctly and the DNS A/AAAA record(s) for that domain contain(s) the right IP address.
    2019-07-28 19:49:14,234:DEBUG:certbot.error_handler:Encountered exception:
    Traceback (most recent call last):
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/auth_handler.py", line 90, in handle_authorizations
        self._poll_authorizations(authzrs, max_retries, best_effort)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/auth_handler.py", line 154, in _poll_authorizations
        raise errors.AuthorizationError('Some challenges have failed.')
    AuthorizationError: Some challenges have failed.

    2019-07-28 19:49:14,234:DEBUG:certbot.error_handler:Calling registered functions
    2019-07-28 19:49:14,234:INFO:certbot.auth_handler:Cleaning up challenges
    2019-07-28 19:49:15,346:DEBUG:certbot.log:Exiting abnormally:
    Traceback (most recent call last):
    File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 11, in <module>
        sys.exit(main())
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/main.py", line 1381, in main
        return config.func(config, plugins)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/main.py", line 1264, in certonly
        lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/main.py", line 120, in _get_and_save_cert
        lineage = le_client.obtain_and_enroll_certificate(domains, certname)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/client.py", line 406, in obtain_and_enroll_certificate
        cert, chain, key, _ = self.obtain_certificate(domains)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/client.py", line 349, in obtain_certificate
        orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/client.py", line 385, in _get_order_and_authorizations
        authzr = self.auth_handler.handle_authorizations(orderr, best_effort)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/auth_handler.py", line 90, in handle_authorizations
        self._poll_authorizations(authzrs, max_retries, best_effort)
    File "/opt/eff.org/certbot/venv/local/lib/python2.7/site-packages/certbot/auth_handler.py", line 154, in _poll_authorizations
        raise errors.AuthorizationError('Some challenges have failed.')
    AuthorizationError: Some challenges have failed.
</details>

I have seen this a few times recently when helping people on the forums, most recently [in this thread](https://community.letsencrypt.org/t/acme-challenge-file-not-found/98654).


Discussion:
To comment on the severity of this issue, it's particularly insidious because it seems to only manifest itself some time after the user initially issues the certificate to secure their origin.

First issuance: Cloudflare can't talk to the origin server over HTTPS, so it falls back to HTTP ("fiexible"). Challenge response is reachable.

At renewal: Cloudflare has invisibly adapted and is now talking to the origin over HTTPS, without the user having changed anything (still "flexible"). Certbot renewal now fails because the challenge response is no longer reachable. From the user's perspective, nothing changed.
It's not automatic -- it happens because the user has manually turned on the "Always Use HTTPS" (redirect HTTP to HTTPS) setting in Cloudflare's dashboard. But, naturally, turning it on sounds like a good idea, and it's not super intuitive that it's causing Certbot to fail 59 days after you might have changed it.

FWIW, this was previously #5764, but it was closed wontfix.
I decided to test it out - you're right, it's not automatic. 

With "Always Use HTTPS" the origin pulls are still over HTTP - looks like that only affects visitor->CDN.

But changing the SSL setting from "Flexible" to "Full" or "Full (Strict)" is what breaks it.
Fixing this is a little tricky (which if I remember correctly is the main reason we decided not to do it in the first place).

The code you want to modify is in `_make_or_mod_server_block` in `http_01.py`. Currently what that code does is it finds the best matching vhost listening on the port provided to `--http-01-port` that is not listening with SSL. If such a vhost isn't found, the representation of one is created in the call to `_make_server_block` and then the caller of `_make_or_mod_server_block` writes it to disk.

You may be able to fix this issue by essentially also always calling `self.configurator.choose_vhosts` which will return a single SSL vhost listening on the port provided to `--https-port` matching the domain. I'd prefer leaving `create_if_no_match=False` for now, but could be convinced otherwise. I'd also personally prefer to add an optional flag called `make_ssl` that defaults to `True` but we set to `False` for our use here for now.

This plan requires some testing, but looking through the code it seems to me like it would work.

@ohemorange, if you have the time and interest to sanity check/improve my suggestion here, I'd certainly appreciate it!
> The code you want to modify is in `_make_or_mod_server_block` in `http_01.py`. Currently what that code does is it finds the best matching vhost listening on the port provided to `--http-01-port` that is not listening with SSL. If such a vhost isn't found, the representation of one is created in the call to `_make_server_block` and then the caller of `_make_or_mod_server_block` writes it to disk.

It's slightly more complicated than this. `create_if_no_match` is set to `True` in the call to `choose_redirect_vhosts`, which means that if no vhost is found that matches on name and port, then it will make a copy of the `default` server block for the right port (which usually exists), and set that up to listen. `_make_server_block` is only called if there's no `default` server block on the right port. So after `choose_redirect_vhosts` is called, there will probably be a perfectly good http vhost sitting around. I think this probably shouldn't matter to the rest of this, though.

> You may be able to fix this issue by essentially also always calling `self.configurator.choose_vhosts` which will return a single SSL vhost listening on the port provided to `--https-port` matching the domain. I'd prefer leaving `create_if_no_match=False` for now, but could be convinced otherwise. I'd also personally prefer to add an optional flag called `make_ssl` that defaults to `True` but we set to `False` for our use here for now.

After calling `self.configurator.choose_vhosts`, you'll probably have to modify the returned vhost in a similar way as the rest of the lines in `_make_or_mod_server_block` do. I'd also want to double check that `self.configurator.choose_vhosts` has the matching behavior we actually want here, because its logic was made for a slightly different use case.

And it's not quite as simple as this... `_make_server_block(achall)` would also have to be modified to make a second, https server block from scratch. And need to figure out if we're only calling that if both `choose_redirect_vhosts` and `self.configurator.choose_vhosts` error out, or just one. I do think `create_if_no_match` would need to be set to `True`.

Not sure what you mean by the `make_ssl` flag, name seems a bit ambiguous. Flag to what?

After all this it might just work, but I wouldn't be shocked if it led to surprise extra things that need to be fixed to get it to work.
Thanks both.

>because its logic was made for a slightly different use case.

The multi-purposeness of those functions seemed a bit awkward when I completed a draft implementation of this a few days ago.

What about ignoring `choose_redirect_vhosts` and `choose_vhosts`? Maybe a function that selects zero or more existing SSL vhosts that match the domain, and then modding them in `_make_or_mod_server_block`?

I would ideally to replicate the vhost selection greediness of what was done in the Apache version of this fix (#6729).
> It's slightly more complicated than this. `create_if_no_match` is set to `True` in the call to `choose_redirect_vhosts`...

Nice catch!

> Not sure what you mean by the `make_ssl` flag, name seems a bit ambiguous. Flag to what?

What I was imagining was a flag to `configurator.choose_vhosts` where if it was false, `_make_server_ssl` wouldn't be called.

> What about ignoring `choose_redirect_vhosts` and `choose_vhosts`? Maybe a function that selects zero or more existing SSL vhosts that match the domain, and then modding them in `_make_or_mod_server_block`?
>
> I would ideally to replicate the vhost selection greediness of what was done in the Apache version of this fix (#6729).

I think we've had slightly different opinions on what the behavior here should look like, but I personally think shooting something similar to #6729 is a good goal.

I'd personally be fine with a separate function for this purpose. I'd just try to avoid duplicating code and maybe refactor things a bit to share code where you think it makes sense.

Resolving patch:
diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py
--- a/certbot-nginx/certbot_nginx/_internal/configurator.py
+++ b/certbot-nginx/certbot_nginx/_internal/configurator.py
@@ -1,3 +1,4 @@
+# pylint: disable=too-many-lines
 """Nginx Configuration"""
 from distutils.version import LooseVersion
 import logging
@@ -15,8 +16,10 @@
 from acme import crypto_util as acme_crypto_util
 from acme.magic_typing import Dict
 from acme.magic_typing import List
+from acme.magic_typing import Optional
 from acme.magic_typing import Set
 from acme.magic_typing import Text
+from acme.magic_typing import Tuple
 from certbot import crypto_util
 from certbot import errors
 from certbot import interfaces
@@ -105,7 +108,7 @@ def __init__(self, *args, **kwargs):
         self.save_notes = ""
 
         # For creating new vhosts if no names match
-        self.new_vhost = None
+        self.new_vhost: Optional[obj.VirtualHost] = None
 
         # List of vhosts configured per wildcard domain on this run.
         # used by deploy_cert() and enhance()
@@ -116,7 +119,7 @@ def __init__(self, *args, **kwargs):
         self._chall_out = 0
 
         # These will be set in the prepare function
-        self.parser = None
+        self.parser: Optional[parser.NginxParser] = None
         self.version = version
         self.openssl_version = openssl_version
         self._enhance_func = {"redirect": self._enable_redirect,
@@ -377,10 +380,13 @@ def ipv6_info(self, port):
                     ipv6only_present = True
         return (ipv6_active, ipv6only_present)
 
-    def _vhost_from_duplicated_default(self, domain, allow_port_mismatch, port):
+    def _vhost_from_duplicated_default(self, domain: str, allow_port_mismatch: bool, port: str
+                                       ) -> obj.VirtualHost:
         """if allow_port_mismatch is False, only server blocks with matching ports will be
            used as a default server block template.
         """
+        assert self.parser is not None # prepare should already have been called here
+
         if self.new_vhost is None:
             default_vhost = self._get_default_vhost(domain, allow_port_mismatch, port)
             self.new_vhost = self.parser.duplicate_vhost(default_vhost,
@@ -509,7 +515,7 @@ def _rank_matches_by_name_and_ssl(self, vhost_list, target_name):
                 match['rank'] += NO_SSL_MODIFIER
         return sorted(matches, key=lambda x: x['rank'])
 
-    def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False):
+    def choose_redirect_vhosts(self, target_name: str, port: str) -> List[obj.VirtualHost]:
         """Chooses a single virtual host for redirect enhancement.
 
         Chooses the vhost most closely matching target_name that is
@@ -523,9 +529,6 @@ def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False):
 
         :param str target_name: domain name
         :param str port: port number
-        :param bool create_if_no_match: If we should create a new vhost from default
-            when there is no match found. If we can't choose a default, raise a
-            MisconfigurationError.
 
         :returns: vhosts associated with name
         :rtype: list of :class:`~certbot_nginx._internal.obj.VirtualHost`
@@ -538,32 +541,75 @@ def choose_redirect_vhosts(self, target_name, port, create_if_no_match=False):
         else:
             matches = self._get_redirect_ranked_matches(target_name, port)
             vhosts = [x for x in [self._select_best_name_match(matches)]if x is not None]
-        if not vhosts and create_if_no_match:
-            vhosts = [self._vhost_from_duplicated_default(target_name, False, port)]
         return vhosts
 
-    def _port_matches(self, test_port, matching_port):
+    def choose_auth_vhosts(self, target_name: str) -> Tuple[List[obj.VirtualHost],
+                                                            List[obj.VirtualHost]]:
+        """Returns a list of HTTP and HTTPS vhosts with a server_name matching target_name.
+
+        If no HTTP vhost exists, one will be cloned from the default vhost. If that fails, no HTTP
+        vhost will be returned.
+
+        :param str target_name: non-wildcard domain name
+
+        :returns: tuple of HTTP and HTTPS virtualhosts
+        :rtype: tuple of :class:`~certbot_nginx._internal.obj.VirtualHost`
+
+        """
+        vhosts = [m['vhost'] for m in self._get_ranked_matches(target_name) if m and 'vhost' in m]
+        http_vhosts = [vh for vh in vhosts if
+                       self._vhost_listening(vh, str(self.config.http01_port), False)]
+        https_vhosts = [vh for vh in vhosts if
+                        self._vhost_listening(vh, str(self.config.https_port), True)]
+
+        # If no HTTP vhost matches, try create one from the default_server on http01_port.
+        if not http_vhosts:
+            try:
+                http_vhosts = [self._vhost_from_duplicated_default(target_name, False,
+                                                                   str(self.config.http01_port))]
+            except errors.MisconfigurationError:
+                http_vhosts = []
+
+        return http_vhosts, https_vhosts
+
+    def _port_matches(self, test_port: str, matching_port: str) -> bool:
         # test_port is a number, matching is a number or "" or None
         if matching_port == "" or matching_port is None:
             # if no port is specified, Nginx defaults to listening on port 80.
             return test_port == self.DEFAULT_LISTEN_PORT
         return test_port == matching_port
 
-    def _vhost_listening_on_port_no_ssl(self, vhost, port):
-        found_matching_port = False
+    def _vhost_listening(self, vhost: obj.VirtualHost, port: str, ssl: bool) -> bool:
+        """Tests whether a vhost has an address listening on a port with SSL enabled or disabled.
+
+        :param `obj.VirtualHost` vhost: The vhost whose addresses will be tested
+        :param port str: The port number as a string that the address should be bound to
+        :param bool ssl: Whether SSL should be enabled or disabled on the address
+
+        :returns: Whether the vhost has an address listening on the port and protocol.
+        :rtype: bool
+
+        """
+        assert self.parser is not None # prepare should already have been called here
+
+        # if the 'ssl on' directive is present on the vhost, all its addresses have SSL enabled
+        all_addrs_are_ssl = self.parser.has_ssl_on_directive(vhost)
+
+        # if we want ssl vhosts: either 'ssl on' or 'addr.ssl' should be enabled
+        # if we want plaintext vhosts: neither 'ssl on' nor 'addr.ssl' should be enabled
+        _ssl_matches = lambda addr: addr.ssl or all_addrs_are_ssl if ssl else \
+                                    not addr.ssl and not all_addrs_are_ssl
+
+        # if there are no listen directives at all, Nginx defaults to
+        # listening on port 80.
         if not vhost.addrs:
-            # if there are no listen directives at all, Nginx defaults to
-            # listening on port 80.
-            found_matching_port = (port == self.DEFAULT_LISTEN_PORT)
-        else:
-            for addr in vhost.addrs:
-                if self._port_matches(port, addr.get_port()) and not addr.ssl:
-                    found_matching_port = True
+            return port == self.DEFAULT_LISTEN_PORT and ssl == all_addrs_are_ssl
+
+        return any(self._port_matches(port, addr.get_port()) and _ssl_matches(addr)
+                   for addr in vhost.addrs)
 
-        if found_matching_port:
-            # make sure we don't have an 'ssl on' directive
-            return not self.parser.has_ssl_on_directive(vhost)
-        return False
+    def _vhost_listening_on_port_no_ssl(self, vhost: obj.VirtualHost, port: str) -> bool:
+        return self._vhost_listening(vhost, port, False)
 
     def _get_redirect_ranked_matches(self, target_name, port):
         """Gets a ranked list of plaintextish port-listening vhosts matching target_name
diff --git a/certbot-nginx/certbot_nginx/_internal/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py
--- a/certbot-nginx/certbot_nginx/_internal/http_01.py
+++ b/certbot-nginx/certbot_nginx/_internal/http_01.py
@@ -5,6 +5,8 @@
 
 from acme import challenges
 from acme.magic_typing import List
+from acme.magic_typing import Optional
+from certbot import achallenges
 from certbot import errors
 from certbot.compat import os
 from certbot.plugins import common
@@ -138,13 +140,12 @@ def _default_listen_addresses(self):
     def _get_validation_path(self, achall):
         return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token"))
 
-    def _make_server_block(self, achall):
+    def _make_server_block(self, achall: achallenges.KeyAuthorizationAnnotatedChallenge) -> List:
         """Creates a server block for a challenge.
+
         :param achall: Annotated HTTP-01 challenge
-        :type achall:
-            :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
-        :param list addrs: addresses of challenged domain
-            :class:`list` of type :class:`~nginx.obj.Addr`
+        :type achall: :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
+
         :returns: server block for the challenge host
         :rtype: list
         """
@@ -172,34 +173,35 @@ def _location_directive_for_achall(self, achall):
         return location_directive
 
 
-    def _make_or_mod_server_block(self, achall):
-        """Modifies a server block to respond to a challenge.
+    def _make_or_mod_server_block(self, achall: achallenges.KeyAuthorizationAnnotatedChallenge
+                                  ) -> Optional[List]:
+        """Modifies server blocks to respond to a challenge. Returns a new HTTP server block
+           to add to the configuration if an existing one can't be found.
 
         :param achall: Annotated HTTP-01 challenge
-        :type achall:
-            :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
+        :type achall: :class:`certbot.achallenges.KeyAuthorizationAnnotatedChallenge`
+
+        :returns: new server block to be added, if any
+        :rtype: list
 
         """
-        try:
-            vhosts = self.configurator.choose_redirect_vhosts(achall.domain,
-                '%i' % self.configurator.config.http01_port, create_if_no_match=True)
-        except errors.MisconfigurationError:
+        http_vhosts, https_vhosts = self.configurator.choose_auth_vhosts(achall.domain)
+
+        new_vhost: Optional[list] = None
+        if not http_vhosts:
             # Couldn't find either a matching name+port server block
             # or a port+default_server block, so create a dummy block
-            return self._make_server_block(achall)
+            new_vhost = self._make_server_block(achall)
 
-        # len is max 1 because Nginx doesn't authenticate wildcards
-        # if len were or vhosts None, we would have errored
-        vhost = vhosts[0]
+        # Modify any existing server blocks
+        for vhost in set(http_vhosts + https_vhosts):
+            location_directive = [self._location_directive_for_achall(achall)]
 
-        # Modify existing server block
-        location_directive = [self._location_directive_for_achall(achall)]
+            self.configurator.parser.add_server_directives(vhost, location_directive)
 
-        self.configurator.parser.add_server_directives(vhost,
-            location_directive)
+            rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
+                                    ' ', '$1', ' ', 'break']]
+            self.configurator.parser.add_server_directives(vhost,
+                rewrite_directive, insert_at_top=True)
 
-        rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
-                                ' ', '$1', ' ', 'break']]
-        self.configurator.parser.add_server_directives(vhost,
-            rewrite_directive, insert_at_top=True)
-        return None
+        return new_vhost
diff --git a/certbot-nginx/certbot_nginx/_internal/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py
--- a/certbot-nginx/certbot_nginx/_internal/parser.py
+++ b/certbot-nginx/certbot_nginx/_internal/parser.py
@@ -10,6 +10,7 @@
 
 from acme.magic_typing import Dict
 from acme.magic_typing import List
+from acme.magic_typing import Optional
 from acme.magic_typing import Set
 from acme.magic_typing import Tuple
 from acme.magic_typing import Union
@@ -361,8 +362,9 @@ def _modify_server_directives(self, vhost, block_func):
         except errors.MisconfigurationError as err:
             raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err)))
 
-    def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False,
-        only_directives=None):
+    def duplicate_vhost(self, vhost_template: obj.VirtualHost,
+                        remove_singleton_listen_params: bool = False,
+                        only_directives: Optional[List] = None) -> obj.VirtualHost:
         """Duplicate the vhost in the configuration files.
 
         :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost_template: The vhost


Test patch:
diff --git a/certbot-nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py
--- a/certbot-nginx/tests/configurator_test.py
+++ b/certbot-nginx/tests/configurator_test.py
@@ -39,7 +39,7 @@ def test_prepare_no_install(self, mock_exe_exists):
 
     def test_prepare(self):
         self.assertEqual((1, 6, 2), self.config.version)
-        self.assertEqual(12, len(self.config.parser.parsed))
+        self.assertEqual(13, len(self.config.parser.parsed))
 
     @mock.patch("certbot_nginx._internal.configurator.util.exe_exists")
     @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen")
@@ -89,7 +89,7 @@ def test_get_all_names(self, mock_gethostbyaddr, mock_gethostname):
             "155.225.50.69.nephoscale.net", "www.example.org", "another.alias",
              "migration.com", "summer.com", "geese.com", "sslon.com",
              "globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com",
-             "headers.com", "example.net"})
+             "headers.com", "example.net", "ssl.both.com"})
 
     def test_supported_enhancements(self):
         self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'],
@@ -935,7 +935,19 @@ def test_choose_vhosts_wildcard_no_ssl_filter_port(self):
                                                 prefer_ssl=False,
                                                 no_ssl_filter_port='80')
             # Check that the dialog was called with only port 80 vhosts
-            self.assertEqual(len(mock_select_vhs.call_args[0][0]), 6)
+            self.assertEqual(len(mock_select_vhs.call_args[0][0]), 8)
+
+    def test_choose_auth_vhosts(self):
+        """choose_auth_vhosts correctly selects duplicative and HTTP/HTTPS vhosts"""
+        http, https = self.config.choose_auth_vhosts('ssl.both.com')
+        self.assertEqual(len(http), 4)
+        self.assertEqual(len(https), 2)
+        self.assertEqual(http[0].names, {'ssl.both.com'})
+        self.assertEqual(http[1].names, {'ssl.both.com'})
+        self.assertEqual(http[2].names, {'ssl.both.com'})
+        self.assertEqual(http[3].names, {'*.both.com'})
+        self.assertEqual(https[0].names, {'ssl.both.com'})
+        self.assertEqual(https[1].names, {'*.both.com'})
 
 
 class InstallSslOptionsConfTest(util.NginxTest):
diff --git a/certbot-nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py
--- a/certbot-nginx/tests/http_01_test.py
+++ b/certbot-nginx/tests/http_01_test.py
@@ -44,6 +44,10 @@ class HttpPerformTest(util.NginxTest):
             challb=acme_util.chall_to_challb(
                 challenges.HTTP01(token=b"kNdwjxOeX0I_A8DXt9Msmg"), "pending"),
             domain="migration.com", account_key=account_key),
+        achallenges.KeyAuthorizationAnnotatedChallenge(
+            challb=acme_util.chall_to_challb(
+                challenges.HTTP01(token=b"kNdwjxOeX0I_A8DXt9Msmg"), "pending"),
+            domain="ipv6ssl.com", account_key=account_key),
     ]
 
     def setUp(self):
@@ -77,8 +81,8 @@ def test_perform2(self):
 
         http_responses = self.http01.perform()
 
-        self.assertEqual(len(http_responses), 4)
-        for i in range(4):
+        self.assertEqual(len(http_responses), 5)
+        for i in range(5):
             self.assertEqual(http_responses[i], acme_responses[i])
 
     def test_mod_config(self):
@@ -105,6 +109,43 @@ def test_mod_config(self):
             #     self.assertEqual(vhost.addrs, set(v_addr2_print))
             # self.assertEqual(vhost.names, set([response.z_domain.decode('ascii')]))
 
+    @mock.patch('certbot_nginx._internal.parser.NginxParser.add_server_directives')
+    def test_mod_config_http_and_https(self, mock_add_server_directives):
+        """A server_name with both HTTP and HTTPS vhosts should get modded in both vhosts"""
+        self.configuration.https_port = 443
+        self.http01.add_chall(self.achalls[3]) # migration.com
+        self.http01._mod_config()  # pylint: disable=protected-access
+
+        # Domain has an HTTP and HTTPS vhost
+        # 2 * 'rewrite' + 2 * 'return 200 keyauthz' = 4
+        self.assertEqual(mock_add_server_directives.call_count, 4)
+
+    @mock.patch('certbot_nginx._internal.parser.nginxparser.dump')
+    @mock.patch('certbot_nginx._internal.parser.NginxParser.add_server_directives')
+    def test_mod_config_only_https(self, mock_add_server_directives, mock_dump):
+        """A server_name with only an HTTPS vhost should get modded"""
+        self.http01.add_chall(self.achalls[4]) # ipv6ssl.com
+        self.http01._mod_config() # pylint: disable=protected-access
+
+        # It should modify the existing HTTPS vhost
+        self.assertEqual(mock_add_server_directives.call_count, 2)
+        # since there was no suitable HTTP vhost or default HTTP vhost, a non-empty one
+        # should have been created and written to the challenge conf file
+        self.assertNotEqual(mock_dump.call_args[0][0], [])
+
+    @mock.patch('certbot_nginx._internal.parser.NginxParser.add_server_directives')
+    def test_mod_config_deduplicate(self, mock_add_server_directives):
+        """A vhost that appears in both HTTP and HTTPS vhosts only gets modded once"""
+        achall = achallenges.KeyAuthorizationAnnotatedChallenge(
+            challb=acme_util.chall_to_challb(
+                challenges.HTTP01(token=b"kNdwjxOeX0I_A8DXt9Msmg"), "pending"),
+            domain="ssl.both.com", account_key=AUTH_KEY)
+        self.http01.add_chall(achall)
+        self.http01._mod_config() # pylint: disable=protected-access
+
+        # Should only get called 5 times, rather than 6, because two vhosts are the same
+        self.assertEqual(mock_add_server_directives.call_count, 5*2)
+
     @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info")
     def test_default_listen_addresses_no_memoization(self, ipv6_info):
         # pylint: disable=protected-access
diff --git a/certbot-nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py
--- a/certbot-nginx/tests/parser_test.py
+++ b/certbot-nginx/tests/parser_test.py
@@ -51,6 +51,7 @@ def test_load(self):
         self.assertEqual({nparser.abs_path(x) for x in
                           ['foo.conf', 'nginx.conf', 'server.conf',
                            'sites-enabled/default',
+                           'sites-enabled/both.com',
                            'sites-enabled/example.com',
                            'sites-enabled/headers.com',
                            'sites-enabled/migration.com',
@@ -88,7 +89,7 @@ def test_filedump(self):
         parsed = nparser._parse_files(nparser.abs_path(
             'sites-enabled/example.com.test'))
         self.assertEqual(3, len(glob.glob(nparser.abs_path('*.test'))))
-        self.assertEqual(9, len(
+        self.assertEqual(10, len(
             glob.glob(nparser.abs_path('sites-enabled/*.test'))))
         self.assertEqual([[['server'], [['listen', '69.50.225.155:9000'],
                                         ['listen', '127.0.0.1'],
@@ -171,7 +172,7 @@ def test_get_vhosts(self):
                                                   '*.www.example.com'},
                                  [], [2, 1, 0])
 
-        self.assertEqual(14, len(vhosts))
+        self.assertEqual(19, len(vhosts))
         example_com = [x for x in vhosts if 'example.com' in x.filep][0]
         self.assertEqual(vhost3, example_com)
         default = [x for x in vhosts if 'default' in x.filep][0]
diff --git a/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/both.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/both.com
new file mode 100644
--- /dev/null
+++ b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/both.com
@@ -0,0 +1,32 @@
+server {
+  server_name ssl.both.com;
+}
+
+# a duplicate vhost
+server {
+  server_name ssl.both.com;
+}
+
+# a duplicate by means of wildcard
+server {
+  server_name *.both.com;
+}
+
+# combined HTTP and HTTPS
+server {
+  server_name ssl.both.com;
+  listen 80;
+  listen 5001 ssl;
+
+  ssl_certificate      cert.pem;
+  ssl_certificate_key  cert.key;
+}
+
+# HTTPS, duplicate by means of wildcard
+server {
+  server_name *.both.com;
+  listen 5001 ssl;
+
+  ssl_certificate      cert.pem;
+  ssl_certificate_key  cert.key;
+}