Setting up SSL for a Python Server with CertBot.
In this article we go over setting up SSL for a Python request server, then adding it to a NGINX reverse-proxy!
Full compliments to these good write ups which gives a simple template to work by: link
- We start with some boilerplate python code:
from http.server import HTTPServer,SimpleHTTPRequestHandler
from socketserver import BaseServer
import ssl
httpd = HTTPServer(('localhost', 1443), SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='certificate.pem', server_side=True)
httpd.serve_forever()
- Alternately after python 3.7 it is recommended to:
from http.server import HTTPServer,SimpleHTTPRequestHandler
import ssl
httpd = HTTPServer(('localhost', 1443), SimpleHTTPRequestHandler)
# Since version 3.10: SSLContext without protocol argument is deprecated.
# sslctx = ssl.SSLContext()
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.check_hostname = False # If set to True, only the hostname that matches the certificate will be accepted
sslctx.load_cert_chain(certfile='certificate.pem', keyfile="private.pem")
httpd.socket = sslctx.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
Once you have your code block the steps can be basic:
- Install certbot and have it generate certs for your domain.
- Obtain 'private.pem' and 'certificate.pem' from a certbot generation.
- Test these in the local environment.
sudo apt install certbot
Now we will stop our local server because certbot requires to temporarily utilize port 80 to verify that you have control over the domain.
certbot certonly --standalone
- Enter all the domains / sub-domains that will serve over SSL
- The certificates will show up in:
/etc/letsencrypt/live
You will have the following files:
README cert.pem chain.pem fullchain.pem privkey.pem
Examining the readme guides us:
This directory contains your keys and certificates.
`privkey.pem` : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem` : will break many server configurations, and should not be used
without reading further documentation (see link below).
WARNING: DO NOT MOVE THESE FILES!
Certbot expects these files to remain in this location in order
to function properly!
We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
We adjust our code as follows:
pwd = os.getcwd()
print(f"Working out of directory: {pwd}")
PORT = 1200
httpd = HTTPServer(('localhost', PORT), SimpleHTTPRequestHandler)
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.check_hostname = False # If set to True, only the hostname that matches the certificate will be accepted
sslctx.load_cert_chain(certfile='../certificate.pem', keyfile="../private.pem")
httpd.socket = sslctx.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
- We are serving out of the html_mth directory, but we do not want our certificates inside of it (remember httpd will serve out of whats in front of it.)
- Once we have this setup we do a https://localhost:1200/index.html to test for content.
- We have used fullchain.pem for our certificate.pem
You will get the certificate warning - we are testing the SSL..
Our page serves!
https://localhost:1201/index.html
It should be understood that the server is directly holding and handling the connection.
Serving from behind a NGINX Proxy.
If we go by the instructions it states:
When NGINX proxies a request, it sends the request to a specified proxied server, fetches the response, and sends it back to the client. It is possible to proxy requests to an HTTP server (another NGINX server or any other server) or a non-HTTP server (which can run an application developed with a specific framework, such as PHP or Python) using a specified protocol.
- It is interpretated that we leave off the SSL behind. We adjust our code to have either option:
encrypting = False
if encrypting:
httpd = HTTPServer(('localhost', PORT), SimpleHTTPRequestHandler)
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.check_hostname = False # If set to True, only the hostname that matches the certificate will be accepted
sslctx.load_cert_chain(certfile='../certificate.pem', keyfile="../private.pem")
httpd.socket = sslctx.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
if not encrypting:
Handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
httpd.serve_forever()
We then set our scheme as:
We can see here that the option to pull the certificate is automatic.
- NGINX Reverse Proxy is now holding the certificate on behalf of the site.
- Update all the sites internal links so they point back at the https://