Skip to content

Commit

Permalink
docs: improve reverse proxy documentation
Browse files Browse the repository at this point in the history
- clarify when REMOTE_ADDR is not available
- log an error when IP address cannot be obtained with link to the
  documentation
- add gunicorn/nginx example
- document starting gunicorn

See also WeblateOrg#13325
  • Loading branch information
nijel committed Dec 19, 2024
1 parent f595bb9 commit 3e7ae55
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 10 deletions.
79 changes: 69 additions & 10 deletions docs/admin/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -691,16 +691,21 @@ Running behind reverse proxy
Several features in Weblate rely on being able to get client IP address. This
includes :ref:`rate-limit`, :ref:`spam-protection` or :ref:`audit-log`.

In default configuration Weblate parses IP address from ``REMOTE_ADDR`` which
is set by the WSGI handler.
Weblate parses IP address from the ``REMOTE_ADDR`` which is set by the WSGI
handler. This might be empty (when using socket for WSGI) or contain a reverse
proxy address, so Weblate needs an additional HTTP header with client IP
address.

In case you are running a reverse proxy, this field will most likely contain
its address. You need to configure Weblate to trust additional HTTP headers and
parse the IP address from these. This can not be enabled by default as it would
allow IP address spoofing for installations not using a reverse proxy. Enabling
:setting:`IP_BEHIND_REVERSE_PROXY` might be enough for the most usual setups,
but you might need to adjust :setting:`IP_PROXY_HEADER` and
:setting:`IP_PROXY_OFFSET` as well.
Enabling :setting:`IP_BEHIND_REVERSE_PROXY` might be enough for the most usual
setups, but you might need to adjust :setting:`IP_PROXY_HEADER` and
:setting:`IP_PROXY_OFFSET` as well (use :envvar:`WEBLATE_IP_PROXY_HEADER` and
:envvar:`WEBLATE_IP_PROXY_OFFSET` in the Docker container).

.. hint::

This configuration cannot be turned on by default because it would allow IP
address spoofing on installations that don't have a properly configured
reverse proxy.

Another thing to take care of is the :http:header:`Host` header. It should match
to whatever is configured as :setting:`SITE_DOMAIN`. Additional configuration
Expand All @@ -712,10 +717,16 @@ for Apache or ``proxy_set_header Host $host;`` with nginx).
:ref:`spam-protection`,
:ref:`rate-limit`,
:ref:`audit-log`,
:ref:`uwsgi`,
:ref:`nginx-gunicorn`,
:ref:`apache`,
:ref:`apache-gunicorn`,
:setting:`IP_BEHIND_REVERSE_PROXY`,
:setting:`IP_PROXY_HEADER`,
:setting:`IP_PROXY_OFFSET`,
:setting:`django:SECURE_PROXY_SSL_HEADER`
:setting:`django:SECURE_PROXY_SSL_HEADER`,
:envvar:`WEBLATE_IP_PROXY_HEADER`,
:envvar:`WEBLATE_IP_PROXY_OFFSET`

HTTP proxy
++++++++++
Expand Down Expand Up @@ -1399,6 +1410,23 @@ configuration, but this might need customization for your environment.
:setting:`CSP_FONT_SRC`
:setting:`CSP_FORM_SRC`

.. _nginx-gunicorn:

Sample configuration for NGINX and Gunicorn
+++++++++++++++++++++++++++++++++++++++++++

The following configuration runs Weblate using Gunicorn under the NGINX webserver
(also available as :file:`weblate/examples/weblate.nginx.gunicorn.conf`):

.. literalinclude:: ../../weblate/examples/weblate.nginx.gunicorn.conf
:language: nginx


.. seealso::

:ref:`running-gunicorn`,
:doc:`django:howto/deployment/wsgi/gunicorn`

.. _uwsgi:

Sample configuration for NGINX and uWSGI
Expand Down Expand Up @@ -1464,11 +1492,42 @@ The following configuration runs Weblate in Gunicorn and Apache 2.4
.. literalinclude:: ../../weblate/examples/apache.gunicorn.conf
:language: apache

.. seealso::

:ref:`running-gunicorn`,
:doc:`django:howto/deployment/wsgi/gunicorn`


.. _running-gunicorn:

Sample configuration to start Gunicorn
++++++++++++++++++++++++++++++++++++++

Weblate has `wsgi` optional dependency (see :ref:`python-deps`) that will
install everything you need to run Gunicorn. When installing Weblate you can specify it as:

.. code-block:: shell
uv pip install Weblate[all,wsgi]
Once you have Gunicorn installed, you can run it. This is usually done at the
system level. The following examples show staring via systemd:

.. literalinclude:: ../../weblate/examples/gunicorn.socket
:caption: /etc/systemd/system/gunicorn.socket
:language: ini

.. literalinclude:: ../../weblate/examples/gunicorn.service
:caption: /etc/systemd/system/gunicorn.service
:language: ini

.. seealso::

:doc:`django:howto/deployment/wsgi/gunicorn`



Running Weblate under path
++++++++++++++++++++++++++

Expand Down
22 changes: 22 additions & 0 deletions weblate/examples/gunicorn.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=weblate
Group=weblate
WorkingDirectory=/home/weblate/weblate-env/
Environment="DJANGO_SETTINGS_MODULE=weblate.settings"
ExecStart=/home/weblate/weblate-env/bin/gunicorn \
--preload \
--timeout 3600 \
--graceful-timeout 3600 \
--worker-class=gthread \
--workers=2 \
--threads=16 \
--bind unix:/run/gunicorn.sock \
weblate.wsgi:application

[Install]
WantedBy=multi-user.target
3 changes: 3 additions & 0 deletions weblate/examples/gunicorn.service.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright © Michal Čihař <[email protected]>

SPDX-License-Identifier: GPL-3.0-or-later
8 changes: 8 additions & 0 deletions weblate/examples/gunicorn.socket
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target
3 changes: 3 additions & 0 deletions weblate/examples/gunicorn.socket.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright © Michal Čihař <[email protected]>

SPDX-License-Identifier: GPL-3.0-or-later
44 changes: 44 additions & 0 deletions weblate/examples/weblate.nginx.gunicorn.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#
# nginx configuration for Weblate
#
# You will want to change:
#
# - server_name
# - change /home/weblate/weblate-env to location where Weblate virtualenv is placed
# - change /home/weblate/data to match your DATA_DIR
# - change /home/weblate/data/cache to match your CACHE_DIR
# - change python3.12 to match your Python version
# - change weblate user to match your Weblate user
#
server {
listen 80;
server_name weblate;
# Not used
root /var/www/html;

location ~ ^/favicon.ico$ {
# CACHE_DIR/static/favicon.ico
alias /home/weblate/data/cache/static/favicon.ico;
expires 30d;
}

location /static/ {
# CACHE_DIR/static/
alias /home/weblate/data/cache/static/;
expires 30d;
}

location /media/ {
# DATA_DIR/media/
alias /home/weblate/data/media/;
expires 30d;
}

location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_pass http://unix:/run/gunicorn.sock;
proxy_read_timeout 3600;
}
}
3 changes: 3 additions & 0 deletions weblate/examples/weblate.nginx.gunicorn.conf.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright © Michal Čihař <[email protected]>

SPDX-License-Identifier: GPL-3.0-or-later
6 changes: 6 additions & 0 deletions weblate/utils/ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from weblate.logger import LOGGER
from weblate.utils import messages
from weblate.utils.cache import is_redis_cache
from weblate.utils.docs import get_doc_url
from weblate.utils.hash import calculate_checksum
from weblate.utils.request import get_ip_address

Expand All @@ -41,6 +42,11 @@ def get_cache_key(
else:
if address is None:
address = get_ip_address(request)
if not address:
LOGGER.error(
"could not obtain remote IP address, see %s",
get_doc_url("admin/install", "reverse-proxy"),
)
origin = "ip"
key = calculate_checksum(address)
return f"ratelimit-{origin}-{scope}-{key}"
Expand Down

0 comments on commit 3e7ae55

Please sign in to comment.