    async def start(self):
        """Start the proxy process"""
        # check if there is a previous instance still around
        self._check_previous_process()

        # build the command to launch
        public_server = Server.from_url(self.public_url)
        api_server = Server.from_url(self.api_url)
        env = os.environ.copy()
        env['CONFIGPROXY_AUTH_TOKEN'] = self.auth_token
        cmd = self.command + [
            '--ip',
            public_server.ip,
            '--port',
            str(public_server.port),
            '--api-ip',
            api_server.ip,
            '--api-port',
            str(api_server.port),
            '--error-target',
            url_path_join(self.hub.url, 'error'),
            '--log-level',
            self.log_level,
        ]
        if self.app.subdomain_host:
            cmd.append('--host-routing')
        if self.ssl_key:
            cmd.extend(['--ssl-key', self.ssl_key])
        if self.ssl_cert:
            cmd.extend(['--ssl-cert', self.ssl_cert])
        if self.app.internal_ssl:
            cmd.extend(self._get_ssl_options())
        # Warn if SSL is not used
        if ' --ssl' not in ' '.join(cmd):
            self.log.warning(
                "Running JupyterHub without SSL."
                "  I hope there is SSL termination happening somewhere else..."
            )
        self.log.info("Starting proxy @ %s", public_server.bind_url)
        self.log.debug("Proxy cmd: %s", cmd)
        shell = os.name == 'nt'
        try:
            self.proxy_process = Popen(
                cmd, env=env, start_new_session=True, shell=shell
            )
        except FileNotFoundError as e:
            self.log.error(
                f"Failed to find proxy {self.command!r}\n"
                "The proxy can be installed with `npm install -g configurable-http-proxy`."
                "To install `npm`, install nodejs which includes `npm`."
                "If you see an `EACCES` error or permissions error, refer to the `npm` "
                "documentation on How To Prevent Permissions Errors."
            )
            raise

        self._write_pid_file()

        async def wait_for_process():
            """Watch proxy process for early termination

            Runs forever, checking every 0.5s if the process has exited
            so we don't keep waiting for endpoints after the proxy has stopped.

            Raises RuntimeError if/when the proxy process exits,
            otherwise runs forever.
            Should be cancelled when servers become ready.
            """
            while True:
                status = self.proxy_process.poll()
                if status is not None:
                    with self.proxy_process:
                        e = RuntimeError(
                            f"Proxy failed to start with exit code {status}"
                        )
                        raise e from None
                await asyncio.sleep(0.5)

        # process_exited can only resolve with a RuntimeError when the process has exited,
        # otherwise it must be cancelled.
        process_exited = asyncio.ensure_future(wait_for_process())

        # wait for both servers to be ready (or one server to fail)
        server_futures = [
            asyncio.ensure_future(server.wait_up(10))
            for server in (public_server, api_server)
        ]
        servers_ready = asyncio.gather(*server_futures)

        # wait for process to crash or servers to be ready,
        # whichever comes first
        wait_timeout = 15
        ready, pending = await asyncio.wait(
            [
                process_exited,
                servers_ready,
            ],
            return_when=asyncio.FIRST_COMPLETED,
            timeout=wait_timeout,
        )
        for task in [servers_ready, process_exited] + server_futures:
            # cancel any pending tasks
            if not task.done():
                task.cancel()
        if not ready:
            # timeouts passed to wait_up should prevent this,
            # but weird things like DNS delays may result in
            # wait_up taking a lot longer than it should
            raise TimeoutError(
                f"Waiting for proxy endpoints didn't complete in {wait_timeout}s"
            )
        if process_exited in ready:
            # process exited, this will raise RuntimeError
            await process_exited
        else:
            # if we got here, servers_ready is done
            # await it to make sure exceptions are raised
            await servers_ready

        self.log.debug("Proxy started and appears to be up")
        pc = PeriodicCallback(self.check_running, 1e3 * self.check_running_interval)
        self._check_running_callback = pc
        pc.start()
Start the proxy process
jupyterhub/proxy.py