Repository: certbot/certbot

Issue:
MisconfigurationError: nginx restart failed ... build server_names_hash
```
...
    include servers-enabled/*;
}

2017-01-28 14:14:20,669:DEBUG:certbot.error_handler:Encountered exception:
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/certbot/auth_handler.py", line 112, in _solve_challenges
    resp = self.auth.perform(self.achalls)
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 818, in perform
    self.restart()
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 603, in restart
    nginx_restart(self.conf('ctl'), self.nginx_conf)
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 865, in nginx_restart
    "nginx restart failed:\n%s\n%s" % (stdout, stderr))
MisconfigurationError: nginx restart failed:

2017/01/28 08:14:20 [emerg] 4619#4619: could not build server_names_hash, you should increase server_names_hash_bucket_
size: 64


2017-01-28 14:14:20,671:DEBUG:certbot.error_handler:Calling registered functions
2017-01-28 14:14:20,671:INFO:certbot.auth_handler:Cleaning up challenges
2017-01-28 14:14:25,210:DEBUG:certbot.main:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/bin/certbot", line 11, in <module>
    load_entry_point('certbot==0.10.1', 'console_scripts', 'certbot')()
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 849, in main
    return config.func(config, plugins)
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 626, in obtain_cert
    action, _ = _auth_from_available(le_client, config, domains, certname, lineage)
  File "/usr/lib/python2.7/site-packages/certbot/main.py", line 107, in _auth_from_available
    lineage = le_client.obtain_and_enroll_certificate(domains, certname)
  File "/usr/lib/python2.7/site-packages/certbot/client.py", line 291, in obtain_and_enroll_certificate
    certr, chain, key, _ = self.obtain_certificate(domains)
  File "/usr/lib/python2.7/site-packages/certbot/client.py", line 262, in obtain_certificate
    self.config.allow_subset_of_names)
  File "/usr/lib/python2.7/site-packages/certbot/auth_handler.py", line 74, in get_authorizations
    resp = self._solve_challenges()
  File "/usr/lib/python2.7/site-packages/certbot/auth_handler.py", line 112, in _solve_challenges
    resp = self.auth.perform(self.achalls)
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 818, in perform
    self.restart()
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 603, in restart
    nginx_restart(self.conf('ctl'), self.nginx_conf)
  File "/usr/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 865, in nginx_restart
    "nginx restart failed:\n%s\n%s" % (stdout, stderr))
MisconfigurationError: nginx restart failed:

2017/01/28 08:14:20 [emerg] 4619#4619: could not build server_names_hash, you should increase server_names_hash_bucket_size: 64
```
yet both "nginx -t" and "nginx -s reload" succeed.

EDIT: I should add that I had experienced the "server_names_hash" before and found that I missed a semi-colon at the end of my "server_name" line.
But in this case, I DID grep recursively through my nginx dir for "server_name" and made sure everything had a ';'


Discussion:
Can you provide a copy of your main `nginx.conf` file? I suspect we're having problems there but a copy of the file containing the `server` block you're trying to enable HTTPS on would also be useful.
nginx.conf (commented stuff removed):
```
user <sanitized>;
worker_processes  1;

error_log  logs/error.log;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    server_names_hash_bucket_size 64;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    gzip  on;

    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:1m;
    ssl_session_tickets off;
    ssl_dhparam '/<sanitized .../...>/<dhparam>.pem';
    resolver 8.8.8.8 8.8.4.4 valid=300s; # Google DNS Servers
    resolver_timeout 5s;
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    include servers-enabled/*;
```
\<included website.conf\>
```
server {
    listen <lan ip>:80;
    listen <lan ip>:443 ssl http2;
    server_name <sanitized host>;
    server_name <sanitized.local.tld>;

    ssl_certificate     '/<sanitized .../...>/<sanitized>.pem';
    ssl_certificate_key '/<sanitized .../...>/<sanitized>.key.pem';

    access_log  logs/<site>.access.log;
    error_log   logs/<site>.error.log;

    client_max_body_size 10M;

    location / {
root /<sanitized test path>/html;
index index.html;
    }
}
server {
    listen 8080;
    listen 8443 ssl http2;
#using abnormal ports because I'm using iptable prerouting (for some reason, I forget why)
    server_name <web.tld>;
    server_name www.<web.tld>;

    access_log  logs/<site>.access.log;
    error_log   logs/<site>.error.log;

    client_max_body_size 10M;

    location / {
root /<sanitized test path>/html;
index index.html;
    }
}
```
$ sudo certbot certonly --nginx -w /\<root www path\> -d \<web.tld\> -d www.<web.tld\>
Thanks. I was able to reproduce the problem.

You can work around the problem by changing:
```
    server_names_hash_bucket_size 64;
```
to
```
    server_names_hash_bucket_size 128;
```
in your `nginx.conf`.

If you're curious why, when authenticating your domain with Let's Encrypt Certbot temporarily creates a server block with with the server name `<64 character hash>.acme.invalid`. This long domain can cause failures according to [Nginx documentation](https://nginx.org/en/docs/http/server_names.html#optimization), so Certbot will temporarily include `server_names_hash_bucket_size 128;` in your configuration file, but only if you haven't already set the directive yourself in your config.

Certbot should do better. Perhaps it should temporarily change the value if it's current value is less than 128? @jsha, this isn't a `boulder` problem, but if you have spare cycles I'm curious your about thoughts because I know you've worked on this before.
Yup, as expected (given the error message), that did it.
I'll leave it to you to close this issue, given the chance someone may account for this scenario in the code, or not.
Yep, @bmw, your solution sounds exactly right.
For the developer who works on this, see `_mod_config()` in `certbot-nginx/certbot_nginx/tls_sni_01.py`.
Is it a duplicate though? Did you grep ALL nginx confs ("includes" as well, if it exists) for that directive?
Maybe the "32" value directive already existed (not just default) before you added another one with "128" set.
Yes, it was a actual duplicate. I had `server_names_hash_bucket_size 64;` earlier in the config without noticing. I deleted my comment.

Resolving patch:
diff --git a/certbot-nginx/certbot_nginx/tls_sni_01.py b/certbot-nginx/certbot_nginx/tls_sni_01.py
--- a/certbot-nginx/certbot_nginx/tls_sni_01.py
+++ b/certbot-nginx/certbot_nginx/tls_sni_01.py
@@ -100,9 +100,13 @@ def _mod_config(self, ll_addrs):
             if line[0] == ['http']:
                 body = line[1]
                 found_bucket = False
+                posn = 0
                 for inner_line in body:
                     if inner_line[0] == bucket_directive[1]:
+                        if int(inner_line[1]) < int(bucket_directive[3]):
+                            body[posn] = bucket_directive
                         found_bucket = True
+                    posn += 1
                 if not found_bucket:
                     body.insert(0, bucket_directive)
                 if include_directive not in body:


Test patch: