From 1c656e17fa82028f722bf36f458f772a2a54e8e8 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 8 Aug 2022 08:27:01 -0700 Subject: [PATCH] modernize deployment docs --- docs/deployment/apache-httpd.rst | 82 +++++++++++++++++ docs/deployment/cgi.rst | 41 --------- docs/deployment/eventlet.rst | 80 +++++++++++++++++ docs/deployment/fastcgi.rst | 138 ----------------------------- docs/deployment/gevent.rst | 80 +++++++++++++++++ docs/deployment/gunicorn.rst | 130 +++++++++++++++++++++++++++ docs/deployment/index.rst | 77 +++++++++++++--- docs/deployment/mod_wsgi.rst | 125 ++++++++++++++------------ docs/deployment/nginx.rst | 87 +++++++++++++++++++ docs/deployment/proxy_fix.rst | 33 +++++++ docs/deployment/proxying.rst | 54 ------------ docs/deployment/uwsgi.rst | 145 +++++++++++++++++++++++++++++++ docs/deployment/waitress.rst | 75 ++++++++++++++++ 13 files changed, 849 insertions(+), 298 deletions(-) create mode 100644 docs/deployment/apache-httpd.rst delete mode 100644 docs/deployment/cgi.rst create mode 100644 docs/deployment/eventlet.rst delete mode 100644 docs/deployment/fastcgi.rst create mode 100644 docs/deployment/gevent.rst create mode 100644 docs/deployment/gunicorn.rst create mode 100644 docs/deployment/nginx.rst create mode 100644 docs/deployment/proxy_fix.rst delete mode 100644 docs/deployment/proxying.rst create mode 100644 docs/deployment/uwsgi.rst create mode 100644 docs/deployment/waitress.rst diff --git a/docs/deployment/apache-httpd.rst b/docs/deployment/apache-httpd.rst new file mode 100644 index 000000000..42fc01fa0 --- /dev/null +++ b/docs/deployment/apache-httpd.rst @@ -0,0 +1,82 @@ +Apache httpd +============ + +`Apache httpd`_ is a fast, production level HTTP server. When serving +your application with one of the WSGI servers listed in :doc:`index`, it +is often good or necessary to put a dedicated HTTP server in front of +it. This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +httpd can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running httpd itself is outside +the scope of this doc. This page outlines the basics of configuring +httpd to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _Apache httpd: https://httpd.apache.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``httpd.conf``. + +Remove or comment out any existing ``DocumentRoot`` directive. Add the +config lines below. We'll assume the WSGI server is listening locally at +``http://127.0.0.1:8000``. + +.. code-block:: apache + :caption: ``/etc/httpd/conf/httpd.conf`` + + LoadModule proxy_module modules/mod_proxy.so + LoadModule proxy_http_module modules/mod_proxy_http.so + ProxyPass / http://127.0.0.1:8000/ + RequestHeader set X-Forwarded-Proto http + RequestHeader set X-Forwarded-Prefix / + +The ``LoadModule`` lines might already exist. If so, make sure they are +uncommented instead of adding them manually. + +Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` +headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically +set by ``ProxyPass``. + + +Static Files +------------ + +If your application has static files such as JavaScript, CSS, and +images, it will be more efficient to let Nginx serve them directly +rather than going through the Python application. + +Assuming the static files are expected to be available under the +``/static/`` URL, and are stored at ``/home/project/static/``, add the +following to the config above. + +.. code-block:: apache + + Alias /static/ /home/project/static/ diff --git a/docs/deployment/cgi.rst b/docs/deployment/cgi.rst deleted file mode 100644 index dae48daeb..000000000 --- a/docs/deployment/cgi.rst +++ /dev/null @@ -1,41 +0,0 @@ -=== -CGI -=== - -If all other deployment methods do not work, CGI will work for sure. CGI -is supported by all major servers but usually has a less-than-optimal -performance. - -This is also the way you can use a Werkzeug application on Google's -`AppEngine`_, there however the execution does happen in a CGI-like -environment. The application's performance is unaffected because of that. - -.. _AppEngine: https://cloud.google.com/appengine/ - -Creating a `.cgi` file -====================== - -First you need to create the CGI application file. Let's call it -`yourapplication.cgi`:: - - #!/usr/bin/python - from wsgiref.handlers import CGIHandler - from yourapplication import make_app - - application = make_app() - CGIHandler().run(application) - -Server Setup -============ - -Usually there are two ways to configure the server. Either just copy the -`.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to -rewrite the URL) or let the server point to the file directly. - -In Apache for example you can put something like this into the config: - -.. sourcecode:: apache - - ScriptAlias /app /path/to/the/application.cgi - -For more information consult the documentation of your webserver. diff --git a/docs/deployment/eventlet.rst b/docs/deployment/eventlet.rst new file mode 100644 index 000000000..243be5ebb --- /dev/null +++ b/docs/deployment/eventlet.rst @@ -0,0 +1,80 @@ +eventlet +======== + +Prefer using :doc:`gunicorn` with eventlet workers rather than using +`eventlet`_ directly. Gunicorn provides a much more configurable and +production-tested server. + +`eventlet`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`gevent` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +eventlet provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use eventlet in +your own code to see any benefit to using the server. + +.. _eventlet: https://eventlet.net/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using eventlet, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install +``eventlet``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install eventlet + + +Running +------- + +To use eventlet to serve your application, write a script that imports +its ``wsgi.server``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + import eventlet + from eventlet import wsgi + from hello import create_app + + app = create_app() + wsgi.server(eventlet.listen(("127.0.0.1", 8000), app) + +.. code-block:: text + + $ python wsgi.py + (x) wsgi starting up on http://127.0.0.1:8000 + + +Binding Externally +------------------ + +eventlet should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of eventlet. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. +Don't do this when using a reverse proxy setup, otherwise it will be +possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/docs/deployment/fastcgi.rst b/docs/deployment/fastcgi.rst deleted file mode 100644 index 8211ea2ad..000000000 --- a/docs/deployment/fastcgi.rst +++ /dev/null @@ -1,138 +0,0 @@ -======= -FastCGI -======= - -A very popular deployment setup on servers like `lighttpd`_ and `nginx`_ -is FastCGI. To use your WSGI application with any of them you will need -a FastCGI server first. - -The most popular one is `flup`_ which we will use for this guide. Make -sure to have it installed. - -Creating a `.fcgi` file -======================= - -First you need to create the FastCGI server file. Let's call it -`yourapplication.fcgi`:: - - #!/usr/bin/python - from flup.server.fcgi import WSGIServer - from yourapplication import make_app - - if __name__ == '__main__': - application = make_app() - WSGIServer(application).run() - -This is enough for Apache to work, however ngingx and older versions of -lighttpd need a socket to be explicitly passed to communicate with the FastCGI -server. For that to work you need to pass the path to the socket to the -:class:`~flup.server.fcgi.WSGIServer`:: - - WSGIServer(application, bindAddress='/path/to/fcgi.sock').run() - -The path has to be the exact same path you define in the server -config. - -Save the `yourapplication.fcgi` file somewhere you will find it again. -It makes sense to have that in `/var/www/yourapplication` or something -similar. - -Make sure to set the executable bit on that file so that the servers -can execute it:: - - # chmod +x /var/www/yourapplication/yourapplication.fcgi - -Configuring lighttpd -==================== - -A basic FastCGI configuration for lighttpd looks like this:: - - fastcgi.server = ("/yourapplication.fcgi" => - (( - "socket" => "/tmp/yourapplication-fcgi.sock", - "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", - "check-local" => "disable", - "max-procs" -> 1 - )) - ) - - alias.url = ( - "/static/" => "/path/to/your/static" - ) - - url.rewrite-once = ( - "^(/static.*)$" => "$1", - "^(/.*)$" => "/yourapplication.fcgi$1" - -Remember to enable the FastCGI, alias and rewrite modules. This configuration -binds the application to `/yourapplication`. - -See the Lighty docs for more information on `FastCGI and Python -`_. - -Configuring nginx -================= - -Installing FastCGI applications on nginx is a bit tricky because by default -some FastCGI parameters are not properly forwarded. - -A basic FastCGI configuration for nginx looks like this:: - - location /yourapplication/ { - include fastcgi_params; - if ($uri ~ ^/yourapplication/(.*)?) { - set $path_url $1; - } - fastcgi_param PATH_INFO $path_url; - fastcgi_param SCRIPT_NAME /yourapplication; - fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; - } - -This configuration binds the application to `/yourapplication`. If you want -to have it in the URL root it's a bit easier because you don't have to figure -out how to calculate `PATH_INFO` and `SCRIPT_NAME`:: - - location /yourapplication/ { - include fastcgi_params; - fastcgi_param PATH_INFO $fastcgi_script_name; - fastcgi_param SCRIPT_NAME ""; - fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; - } - -Since Nginx doesn't load FastCGI apps, you have to do it by yourself. You -can either write an `init.d` script for that or execute it inside a screen -session:: - - $ screen - $ /var/www/yourapplication/yourapplication.fcgi - -Debugging -========= - -FastCGI deployments tend to be hard to debug on most webservers. Very often the -only thing the server log tells you is something along the lines of "premature -end of headers". In order to debug the application the only thing that can -really give you ideas why it breaks is switching to the correct user and -executing the application by hand. - -This example assumes your application is called `application.fcgi` and that your -webserver user is `www-data`:: - - $ su www-data - $ cd /var/www/yourapplication - $ python application.fcgi - Traceback (most recent call last): - File "yourapplication.fcg", line 4, in - ImportError: No module named yourapplication - -In this case the error seems to be "yourapplication" not being on the python -path. Common problems are: - -- relative paths being used. Don't rely on the current working directory -- the code depending on environment variables that are not set by the - web server. -- different python interpreters being used. - -.. _lighttpd: https://www.lighttpd.net/ -.. _nginx: https://nginx.org/ -.. _flup: https://pypi.org/project/flup/ diff --git a/docs/deployment/gevent.rst b/docs/deployment/gevent.rst new file mode 100644 index 000000000..aae63e89e --- /dev/null +++ b/docs/deployment/gevent.rst @@ -0,0 +1,80 @@ +gevent +====== + +Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather +than using `gevent`_ directly. Gunicorn and uWSGI provide much more +configurable and production-tested servers. + +`gevent`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`eventlet` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +gevent provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use gevent in your +own code to see any benefit to using the server. + +.. _gevent: https://www.gevent.org/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install ``gevent``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install gevent + + +Running +------- + +To use gevent to serve your application, write a script that imports its +``WSGIServer``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + from gevent.pywsgi import WSGIServer + from hello import create_app + + app = create_app() + http_server = WSGIServer(("127.0.0.1", 8000), app) + http_server.serve_forever() + +.. code-block:: text + + $ python wsgi.py + +No output is shown when the server starts. + + +Binding Externally +------------------ + +gevent should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of gevent. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. Don't +do this when using a reverse proxy setup, otherwise it will be possible +to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/docs/deployment/gunicorn.rst b/docs/deployment/gunicorn.rst new file mode 100644 index 000000000..82fe8ab75 --- /dev/null +++ b/docs/deployment/gunicorn.rst @@ -0,0 +1,130 @@ +Gunicorn +======== + +`Gunicorn`_ is a pure Python WSGI server with simple configuration and +multiple worker implementations for performance tuning. + +* It tends to integrate easily with hosting platforms. +* It does not support Windows (but does run on WSL). +* It is easy to install as it does not require additional dependencies + or compilation. +* It has built-in async worker support using gevent or eventlet. + +This page outlines the basics of running Gunicorn. Be sure to read its +`documentation`_ and use ``gunicorn --help`` to understand what features +are available. + +.. _Gunicorn: https://gunicorn.org/ +.. _documentation: https://docs.gunicorn.org/ + + +Installing +---------- + +Gunicorn is easy to install, as it does not require external +dependencies or compilation. It runs on Windows only under WSL. + +Create a virtualenv, install your application, then install +``gunicorn``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install gunicorn + + +Running +------- + +The only required argument to Gunicorn tells it how to load your +application. The syntax is ``{module_import}:{app_variable}``. +``module_import`` is the dotted import name to the module with your +application. ``app_variable`` is the variable with the application. It +can also be a function call (with any arguments) if you're using the +app factory pattern. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ gunicorn -w 4 'hello:app' + + # equivalent to 'from hello import create_app; create_app()' + $ gunicorn -w 4 'hello:create_app()' + + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: sync + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + +The ``-w`` option specifies the number of processes to run; a starting +value could be ``CPU * 2``. The default is only 1 worker, which is +probably not what you want for the default worker type. + +Logs for each request aren't shown by default, only worker info and +errors are shown. To show access logs on stdout, use the +``--access-logfile=-`` option. + + +Binding Externally +------------------ + +Gunicorn should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Gunicorn. + +You can bind to all external IPs on a non-privileged port using the +``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup, +otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' + Listening at: http://0.0.0.0:8000 (x) + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent or eventlet +----------------------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, Gunicorn provides workers using either `gevent`_ +or `eventlet`_. This is not the same as Python's ``async/await``, or the +ASGI server spec. You must actually use gevent/eventlet in your own code +to see any benefit to using the workers. + +When using either gevent or eventlet, greenlet>=1.0 is required, +otherwise context locals such as ``request`` will not work as expected. +When using PyPy, PyPy>=7.3.7 is required. + +To use gevent: + +.. code-block:: text + + $ gunicorn -k gevent 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: gevent + Booting worker with pid: x + +To use eventlet: + +.. code-block:: text + + $ gunicorn -k eventlet 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: eventlet + Booting worker with pid: x + +.. _gevent: https://www.gevent.org/ +.. _eventlet: https://eventlet.net/ diff --git a/docs/deployment/index.rst b/docs/deployment/index.rst index 012b171b4..f884f080e 100644 --- a/docs/deployment/index.rst +++ b/docs/deployment/index.rst @@ -1,14 +1,71 @@ -====================== -Application Deployment -====================== +Deploying to Production +======================= -This section covers running your application in production on a web -server such as Apache or lighttpd. +After developing your application, you'll want to make it available +publicly to other users. When you're developing locally, you're probably +using the built-in development server, debugger, and reloader. These +should not be used in production. Instead, you should use a dedicated +WSGI server or hosting platform, some of which will be described here. + +"Production" means "not development", which applies whether you're +serving your application publicly to millions of users or privately / +locally to a single user. **Do not use the development server when +deploying to production. It is intended for use only during local +development. It is not designed to be particularly secure, stable, or +efficient.** + +Self-Hosted Options +------------------- + +Werkzeug is a WSGI *application*. A WSGI *server* is used to run the +application, converting incoming HTTP requests to the standard WSGI +environ, and converting outgoing WSGI responses to HTTP responses. + +The primary goal of these docs is to familiarize you with the concepts +involved in running a WSGI application using a production WSGI server +and HTTP server. There are many WSGI servers and HTTP servers, with many +configuration possibilities. The pages below discuss the most common +servers, and show the basics of running each one. The next section +discusses platforms that can manage this for you. + +.. toctree:: + :maxdepth: 1 + + gunicorn + waitress + mod_wsgi + uwsgi + gevent + eventlet + +WSGI servers have HTTP servers built-in. However, a dedicated HTTP +server may be safer, more efficient, or more capable. Putting an HTTP +server in front of the WSGI server is called a "reverse proxy." .. toctree:: - :maxdepth: 2 + :maxdepth: 1 + + proxy_fix + nginx + apache-httpd + +This list is not exhaustive, and you should evaluate these and other +servers based on your application's needs. Different servers will have +different capabilities, configuration, and support. + + +Hosting Platforms +----------------- + +There are many services available for hosting web applications without +needing to maintain your own server, networking, domain, etc. Some +services may have a free tier up to a certain time or bandwidth. Many of +these services use one of the WSGI servers described above, or a similar +interface. + +You should evaluate services based on your application's needs. +Different services will have different capabilities, configuration, +pricing, and support. - cgi - mod_wsgi - fastcgi - proxying +You'll probably need to :doc:`proxy_fix` when using most hosting +platforms. diff --git a/docs/deployment/mod_wsgi.rst b/docs/deployment/mod_wsgi.rst index 93d48a96e..f32631bdf 100644 --- a/docs/deployment/mod_wsgi.rst +++ b/docs/deployment/mod_wsgi.rst @@ -1,79 +1,94 @@ -=================== -`mod_wsgi` (Apache) -=================== +mod_wsgi +======== -If you are using the `Apache`_ webserver you should consider using `mod_wsgi`_. +`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. +The modern `mod_wsgi-express`_ command makes it easy to configure and +start the server without needing to write Apache httpd configuration. -.. _Apache: https://httpd.apache.org/ +* Tightly integrated with Apache httpd. +* Supports Windows directly. +* Requires a compiler and the Apache development headers to install. +* Does not require a reverse proxy setup. -Installing `mod_wsgi` -===================== +This page outlines the basics of running mod_wsgi-express, not the more +complex installation and configuration with httpd. Be sure to read the +`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to +understand what features are available. -If you don't have `mod_wsgi` installed yet you have to either install it using -a package manager or compile it yourself. +.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/ +.. _Apache httpd: https://httpd.apache.org/ -The mod_wsgi `installation instructions`_ cover installation instructions for -source installations on UNIX systems. -If you are using ubuntu / debian you can apt-get it and activate it as follows:: +Installing +---------- - # apt-get install libapache2-mod-wsgi +Installing mod_wsgi requires a compiler and the Apache server and +development headers installed. You will get an error if they are not. +How to install them depends on the OS and package manager that you use. -On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by using -pkg_add:: +Create a virtualenv, install your application, then install +``mod_wsgi``. - # pkg_add -r mod_wsgi +.. code-block:: text -If you are using pkgsrc you can install `mod_wsgi` by compiling the -`www/ap2-wsgi` package. + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install mod_wsgi -If you encounter segfaulting child processes after the first apache reload you -can safely ignore them. Just restart the server. -Creating a `.wsgi` file -======================= +Running +------- -To run your application you need a `yourapplication.wsgi` file. This file -contains the code `mod_wsgi` is executing on startup to get the application -object. The object called `application` in that file is then used as -application. +The only argument to ``mod_wsgi-express`` specifies a script containing +your application, which must be called ``application``. You can +write a small script to import your app with this name, or to create it +if using the app factory pattern. -For most applications the following file should be sufficient:: +.. code-block:: python + :caption: ``wsgi.py`` - from yourapplication import make_app - application = make_app() + from hello import app -If you don't have a factory function for application creation but a singleton -instance you can directly import that one as `application`. + application = app -Store that file somewhere where you will find it again (eg: -`/var/www/yourapplication`) and make sure that `yourapplication` and all -the libraries that are in use are on the python load path. If you don't -want to install it system wide consider using a `virtual python`_ instance. +.. code-block:: python + :caption: ``wsgi.py`` -Configuring Apache -================== + from hello import create_app -The last thing you have to do is to create an Apache configuration file for -your application. In this example we are telling `mod_wsgi` to execute the -application under a different user for security reasons: + application = create_app() -.. sourcecode:: apache +Now run the ``mod_wsgi-express start-server`` command. - - ServerName example.com +.. code-block:: text - WSGIDaemonProcess yourapplication user=user1 group=group1 processes=2 threads=5 - WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi + $ mod_wsgi-express start-server wsgi.py --processes 4 - - WSGIProcessGroup yourapplication - WSGIApplicationGroup %{GLOBAL} - Order deny,allow - Allow from all - - +The ``--processes`` option specifies the number of worker processes to +run; a starting value could be ``CPU * 2``. -.. _mod_wsgi: https://modwsgi.readthedocs.io/en/develop/ -.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html -.. _virtual python: https://pypi.org/project/virtualenv/ +Logs for each request aren't show in the terminal. If an error occurs, +its information is written to the error log file shown when starting the +server. + + +Binding Externally +------------------ + +Unlike the other WSGI servers in these docs, mod_wsgi can be run as +root to bind to privileged ports like 80 and 443. However, it must be +configured to drop permissions to a different user and group for the +worker processes. + +For example, if you created a ``hello`` user and group, you should +install your virtualenv and application as that user, then tell +mod_wsgi to drop to that user after starting. + +.. code-block:: text + + $ sudo /home/hello/venv/bin/mod_wsgi-express start-server \ + /home/hello/wsgi.py \ + --user hello --group hello --port 80 --processes 4 diff --git a/docs/deployment/nginx.rst b/docs/deployment/nginx.rst new file mode 100644 index 000000000..5b136e4d0 --- /dev/null +++ b/docs/deployment/nginx.rst @@ -0,0 +1,87 @@ +nginx +===== + +`nginx`_ is a fast, production level HTTP server. When serving your +application with one of the WSGI servers listed in :doc:`index`, it is +often good or necessary to put a dedicated HTTP server in front of it. +This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +Nginx can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running Nginx itself is outside +the scope of this doc. This page outlines the basics of configuring +Nginx to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _nginx: https://nginx.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The nginx configuration is located at ``/etc/nginx/nginx.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``nginx.conf``. + +Remove or comment out any existing ``server`` section. Add a ``server`` +section and use the ``proxy_pass`` directive to point to the address the +WSGI server is listening on. We'll assume the WSGI server is listening +locally at ``http://127.0.0.1:8000``. + +.. code-block:: nginx + :caption: ``/etc/nginx.conf`` + + server { + listen 80; + server_name _; + + location / { + proxy_pass http://127.0.0.1:8000/; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Prefix /; + } + } + +Then :doc:`proxy_fix` so that your application uses these headers. + + +Static Files +------------ + +If your application has static files such as JavaScript, CSS, and +images, it will be more efficient to let Nginx serve them directly +rather than going through the Python application. + +Assuming the static files are expected to be available under the +``/static/`` URL, and are stored at ``/home/project/static/``, add the +following to the ``server`` block above. + +.. code-block:: nginx + + location /static { + alias /home/project/static; + } diff --git a/docs/deployment/proxy_fix.rst b/docs/deployment/proxy_fix.rst new file mode 100644 index 000000000..7b163b1fc --- /dev/null +++ b/docs/deployment/proxy_fix.rst @@ -0,0 +1,33 @@ +Tell Werkzeug it is Behind a Proxy +================================== + +When using a reverse proxy, or many Python hosting platforms, the proxy +will intercept and forward all external requests to the local WSGI +server. + +From the WSGI server and application's perspectives, requests are now +coming from the HTTP server to the local address, rather than from +the remote address to the external server address. + +HTTP servers should set ``X-Forwarded-`` headers to pass on the real +values to the application. The application can then be told to trust and +use those values by wrapping it with the +:doc:`../middleware/proxy_fix` middleware provided by Werkzeug. + +This middleware should only be used if the application is actually +behind a proxy, and should be configured with the number of proxies that +are chained in front of it. Not all proxies set all the headers. Since +incoming headers can be faked, you must set how many proxies are setting +each header so the middleware knows what to trust. + +.. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + + app.wsgi_app = ProxyFix( + app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + +Remember, only apply this middleware if you are behind a proxy, and set +the correct number of proxies that set each header. It can be a security +issue if you get this configuration wrong. diff --git a/docs/deployment/proxying.rst b/docs/deployment/proxying.rst deleted file mode 100644 index d3e47c0c4..000000000 --- a/docs/deployment/proxying.rst +++ /dev/null @@ -1,54 +0,0 @@ -============= -HTTP Proxying -============= - -Many people prefer using a standalone Python HTTP server and proxying that -server via nginx, Apache etc. - -A very stable Python server is CherryPy. This part of the documentation -shows you how to combine your WSGI application with the CherryPy WSGI -server and how to configure the webserver for proxying. - - -Creating a `.py` server -======================= - -To run your application you need a `start-server.py` file that starts up -the WSGI Server. - -It looks something along these lines:: - - from cherrypy import wsgiserver - from yourapplication import make_app - server = wsgiserver.CherryPyWSGIServer(('localhost', 8080), make_app()) - try: - server.start() - except KeyboardInterrupt: - server.stop() - -If you now start the file the server will listen on `localhost:8080`. Keep -in mind that WSGI applications behave slightly different for proxied setups. -If you have not developed your application for proxying in mind, you can -apply the :class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware. - - -Configuring nginx -================= - -As an example we show here how to configure nginx to proxy to the server. - -The basic nginx configuration looks like this:: - - location / { - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://127.0.0.1:8080; - proxy_redirect default; - } - -Since Nginx doesn't start your server for you, you have to do it by yourself. You -can either write an `init.d` script for that or execute it inside a screen -session:: - - $ screen - $ python start-server.py diff --git a/docs/deployment/uwsgi.rst b/docs/deployment/uwsgi.rst new file mode 100644 index 000000000..2da5efe2d --- /dev/null +++ b/docs/deployment/uwsgi.rst @@ -0,0 +1,145 @@ +uWSGI +===== + +`uWSGI`_ is a fast, compiled server suite with extensive configuration +and capabilities beyond a basic server. + +* It can be very performant due to being a compiled program. +* It is complex to configure beyond the basic application, and has so + many options that it can be difficult for beginners to understand. +* It does not support Windows (but does run on WSL). +* It requires a compiler to install in some cases. + +This page outlines the basics of running uWSGI. Be sure to read its +documentation to understand what features are available. + +.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ + + +Installing +---------- + +uWSGI has multiple ways to install it. The most straightforward is to +install the ``pyuwsgi`` package, which provides precompiled wheels for +common platforms. However, it does not provide SSL support, which can be +provided with a reverse proxy instead. + +Create a virtualenv, install your application, then install ``pyuwsgi``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install pyuwsgi + +If you have a compiler available, you can install the ``uwsgi`` package +instead. Or install the ``pyuwsgi`` package from sdist instead of wheel. +Either method will include SSL support. + +.. code-block:: text + + $ pip install uwsgi + + # or + $ pip install --no-binary pyuwsgi pyuwsgi + + +Running +------- + +The most basic way to run uWSGI is to tell it to start an HTTP server +and import your application. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: preforking *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 1) + spawned uWSGI worker 2 (pid: x, cores: 1) + spawned uWSGI worker 3 (pid: x, cores: 1) + spawned uWSGI worker 4 (pid: x, cores: 1) + spawned uWSGI http 1 (pid: x) + +If you're using the app factory pattern, you'll need to create a small +Python file to create the app, then point uWSGI at that. + +.. code-block:: python + :caption: ``wsgi.py`` + + from hello import create_app + + app = create_app() + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app + +The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The +``--master`` option specifies the standard worker manager. The ``-p`` +option starts 4 worker processes; a starting value could be ``CPU * 2``. +The ``-w`` option tells uWSGI how to import your application + + +Binding Externally +------------------ + +uWSGI should not be run as root with the configuration shown in this doc +because it would cause your application code to run as root, which is +not secure. However, this means it will not be possible to bind to port +80 or 443. Instead, a reverse proxy such as :doc:`nginx` or +:doc:`apache-httpd` should be used in front of uWSGI. It is possible to +run uWSGI as root securely, but that is beyond the scope of this doc. + +uWSGI has optimized integration with `Nginx uWSGI`_ and +`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using +a standard HTTP proxy. That configuration is beyond the scope of this +doc, see the links for more information. + +.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html +.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi + +You can bind to all external IPs on a non-privileged port using the +``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy +setup, otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent +----------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, uWSGI provides a `gevent`_ worker. This is not the +same as Python's ``async/await``, or the ASGI server spec. You must +actually use gevent in your own code to see any benefit to using the +worker. + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: async *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 100) + spawned uWSGI http 1 (pid: x) + *** running gevent loop engine [addr:x] *** + + +.. _gevent: https://www.gevent.org/ diff --git a/docs/deployment/waitress.rst b/docs/deployment/waitress.rst new file mode 100644 index 000000000..b44223d2d --- /dev/null +++ b/docs/deployment/waitress.rst @@ -0,0 +1,75 @@ +Waitress +======== + +`Waitress`_ is a pure Python WSGI server. + +* It is easy to configure. +* It supports Windows directly. +* It is easy to install as it does not require additional dependencies + or compilation. +* It does not support streaming requests, full request data is always + buffered. +* It uses a single process with multiple thread workers. + +This page outlines the basics of running Waitress. Be sure to read its +documentation and ``waitress-serve --help`` to understand what features +are available. + +.. _Waitress: https://docs.pylonsproject.org/projects/waitress/ + + +Installing +---------- + +Create a virtualenv, install your application, then install +``waitress``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install waitress + + +Running +------- + +The only required argument to ``waitress-serve`` tells it how to load +your application. The syntax is ``{module}:{app}``. ``module`` is +the dotted import name to the module with your application. ``app`` is +the variable with the application. If you're using the app factory +pattern, use ``--call {module}:{factory}`` instead. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ waitress-serve hello:app --host 127.0.0.1 + + # equivalent to 'from hello import create_app; create_app()' + $ waitress-serve --call hello:create_app --host 127.0.0.1 + + Serving on http://127.0.0.1:8080 + +The ``--host`` option binds the server to local ``127.0.0.1`` only. + +Logs for each request aren't shown, only errors are shown. Logging can +be configured through the Python interface instead of the command line. + + +Binding Externally +------------------ + +Waitress should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Waitress. + +You can bind to all external IPs on a non-privileged port by not +specifying the ``--host`` option. Don't do this when using a revers +proxy setup, otherwise it will be possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser.