Use python -m http.server in SSL
python -m http.server
is a very convenient command line tool to start an ad-hoc static web server. This tool is very useful when you want to develop small web applications or serve static files.
But it does not support SSL. A lot of modern web applications as well as browser features require a secured connection. So I want to wrap http.server
in SSL and try making it simple to use.
Use http.server
with SSL
I modified several functions from the module http.server
and write a script ssl_server.py
. I’ll show you how to use the script and mkcert
to serve a static sites with a self-signed SSL certificate.
The first step is to copy the ssl_server.py
script to ~/.local/bin
and make it executable.
cp ssl_server.py ~/.local/bin
chmod +x ~/.local/bin/ssl_server.py
Then install the mkcert
tool. You can find the installation instructions on the mkcert GitHub page.
Generate a self-signed certificate and a private key using mkcert
(for a domain like example.local).
mkcert example.local
The command will generate two files example.local.pem
and example.local-key.pem
. You can use these files to start the SSL server.
~/.local/bin/ssl_server.py --cert example.local.pem --key example.local-key.pem --port 8443
Now that you have a static web server running on https://example.local:8443
.
To visit the sites in your browser, you need to do 2 more things:
- add the domain
example.local
to your/etc/hosts
file.
echo "127.0.0.1 example.local" | sudo tee -a /etc/hosts
- Trust the certificate in your browser.
# Install using mkcert
mkcert -install
# Or install manually from mkcert root CA directory
mkcert -CAROOT
# copy the root CA file
The ssl_server.py
script
The script is as follows:
#! /usr/bin/env python
'''
A wrapper around the standard library's http.server module that adds SSL support.
'''
import sys
import os
import socket
import ssl
from http.server import (
SimpleHTTPRequestHandler,
CGIHTTPRequestHandler,
ThreadingHTTPServer,
BaseHTTPRequestHandler,
_get_best_family
)
def test(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=None,
cert=None, key=None
):
"""Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the port argument).
"""
ServerClass.address_family, addr = _get_best_family(bind, port)
HandlerClass.protocol_version = protocol
with ServerClass(addr, HandlerClass) as httpd:
if cert and key:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(cert, key)
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
host, port = httpd.socket.getsockname()[:2]
url_host = f'[{host}]' if ':' in host else host
print(
f"Serving HTTP on {host} port {port} "
f"(http://{url_host}:{port}/) ..."
)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
sys.exit(0)
if __name__ == '__main__':
import argparse
import contextlib
parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
help='run as CGI server')
parser.add_argument('--bind', '-b', metavar='ADDRESS',
help='specify alternate bind address '
'(default: all interfaces)')
parser.add_argument('--directory', '-d', default=os.getcwd(),
help='specify alternate directory '
'(default: current directory)')
parser.add_argument('--port', action='store', default=8000, type=int,
nargs='?',
help='specify alternate port (default: 8000)')
parser.add_argument('--cert', help='specify a certificate file')
parser.add_argument('--key', help='specify a private key file')
args = parser.parse_args()
if args.cgi:
handler_class = CGIHTTPRequestHandler
else:
handler_class = SimpleHTTPRequestHandler
# ensure dual-stack is not disabled; ref #38907
class DualStackServer(ThreadingHTTPServer):
def server_bind(self):
# suppress exception when protocol is IPv4
with contextlib.suppress(Exception):
self.socket.setsockopt(
socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
return super().server_bind()
def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self,
directory=args.directory)
test(
HandlerClass=handler_class,
ServerClass=DualStackServer,
port=args.port,
bind=args.bind,
cert=args.cert,
key=args.key
)
The script wraps the httpd.socket
with an SSL socket if the cert
and key
options are provided.
The script keeps the same interface as http.server
, but it adds two more options --cert
and --key
which are used to specify the certificate and the private key files.
Generally, you can use the script like this:
python ssl_server.py --cert cert.pem --key key.pem
For convenience, you may put cert and key files in a directory and use a bash script to start the server.
#!/bin/bash
cert_dir=~/certs # change to your cert directory
cert=$cert_dir/cert.pem
key=$cert_dir/key.pem
python ssl_server.py --cert $cert --key $key $@