Repository: certbot/certbot

Issue:
Stop printing nginx: [error] invalid PID number "" in "/run/nginx.pid"
If you use Certbot to obtain a certificate using the nginx plugin and nginx is initially off, you should see output like:
```
nginx: [error] invalid PID number "" in "/run/nginx.pid"
```
As part of our results from user testing, we should remove this output.

This should be verified, but my understanding here is this comes from [this call to `nginx -s reload`](https://github.com/certbot/certbot/blob/2a047eb526ee02d4bb3bb3668260ac6d481b99d3/certbot-nginx/certbot_nginx/_internal/configurator.py#L1178-L1179) where stdout/stderr isn't captured. This command fails and then we try starting nginx which works.

If this understanding is correct, I think we should capture the output from the first command and either:

1. Log it at the debug level.
2. Hold it in a local variable to be logged/raised in an exception if the command starting nginx also fails.


Discussion:

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
@@ -1175,23 +1175,29 @@ def nginx_restart(nginx_ctl, nginx_conf, sleep_duration):
 
     """
     try:
-        proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"],
-                                env=util.env_no_snap_for_external_calls())
-        proc.communicate()
+        reload_output = "" # type: unicode
+        with tempfile.TemporaryFile() as out:
+            proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"],
+                                    env=util.env_no_snap_for_external_calls(),
+                                    stdout=out, stderr=out)
+            proc.communicate()
+            out.seek(0)
+            reload_output = out.read().decode("utf-8")
 
         if proc.returncode != 0:
-            # Maybe Nginx isn't running
+            logger.debug("nginx reload failed:\n%s", reload_output)
+            # Maybe Nginx isn't running - try start it
             # Write to temporary files instead of piping because of communication issues on Arch
             # https://github.com/certbot/certbot/issues/4324
             with tempfile.TemporaryFile() as out:
-                with tempfile.TemporaryFile() as err:
-                    nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf],
-                        stdout=out, stderr=err, env=util.env_no_snap_for_external_calls())
-                    nginx_proc.communicate()
-                    if nginx_proc.returncode != 0:
-                        # Enter recovery routine...
-                        raise errors.MisconfigurationError(
-                            "nginx restart failed:\n%s\n%s" % (out.read(), err.read()))
+                nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf],
+                    stdout=out, stderr=out, env=util.env_no_snap_for_external_calls())
+                nginx_proc.communicate()
+                if nginx_proc.returncode != 0:
+                    out.seek(0)
+                    # Enter recovery routine...
+                    raise errors.MisconfigurationError(
+                        "nginx restart failed:\n%s" % out.read().decode("utf-8"))
 
     except (OSError, ValueError):
         raise errors.MisconfigurationError("nginx restart failed")


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
@@ -466,14 +466,18 @@ def test_nginx_restart(self, mock_time, mock_popen):
         mocked.communicate.return_value = ('', '')
         mocked.returncode = 0
         self.config.restart()
+        self.assertEqual(mocked.communicate.call_count, 1)
         mock_time.sleep.assert_called_once_with(0.1234)
 
     @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen")
-    def test_nginx_restart_fail(self, mock_popen):
+    @mock.patch("certbot_nginx._internal.configurator.logger.debug")
+    def test_nginx_restart_fail(self, mock_log_debug, mock_popen):
         mocked = mock_popen()
         mocked.communicate.return_value = ('', '')
         mocked.returncode = 1
         self.assertRaises(errors.MisconfigurationError, self.config.restart)
+        self.assertEqual(mocked.communicate.call_count, 2)
+        mock_log_debug.assert_called_once_with("nginx reload failed:\n%s", "")
 
     @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen")
     def test_no_nginx_start(self, mock_popen):