| Title: URL filtering HTTP(S) proxy on Qubes OS | |
| Author: Solène | |
| Date: 29 August 2025 | |
| Tags: qubes qubesos security squid | |
| Description: In this article, you will learn how to configure a squid | |
| proxy on Qubes OS to filter outgoing http(s) queries. | |
| # Preamble | |
| This article was first published as a community guide on Qubes OS | |
| forum. Both are kept in sync. | |
| => https://forum.qubes-os.org/t/url-filtering-https-proxy/35846 | |
| # Introduction | |
| This guide is meant to users who want to allow a qube to reach some | |
| websites but not all the Internet, but facing the issue that using the | |
| firewall does not work well for DNS names using often changing IPs. | |
| ⚠️ This guide is for advanced users who understand what a HTTP(s) | |
| proxy is, and how to type commands or edit files in a terminal. | |
| The setup will create a `sys-proxy-out` qube that will define a list of | |
| allowed domains, and use qvm-connect-tcp to allow client qubes to use | |
| it as a proxy. Those qubes could have no netvm, but still reach the | |
| filtered websites. | |
| I based it on debian 12 xfce, so it's easy to set up and will be | |
| supported long term. | |
| # Use case | |
| * an offline qube that need to reach a particular website | |
| * a web browsing qube restricted to a list of websites | |
| * mix multiple netvm / VPNs into a single qube | |
| # Setup the template | |
| * Install debian-12-xfce template | |
| * Make a clone of it, let's call it debian-12-xfce-squid | |
| * Start the qube and open a terminal | |
| * Type `sudo apt install -y squid` | |
| * Delete and replace `/etc/squid/squid.conf` with this content (the | |
| default file is not suitable at all) | |
| ``` | |
| acl localnet src 127.0.0.1/32 | |
| acl SSL_ports port 443 | |
| acl Safe_ports port 80 | |
| acl Safe_ports port 443 | |
| http_access deny !Safe_ports | |
| http_access deny CONNECT !SSL_ports | |
| acl permit_list dstdomain '/rw/config/domains.txt' | |
| http_access allow localnet permit_list | |
| http_port 3128 | |
| cache deny all | |
| logfile_rotate 0 | |
| coredump_dir /var/spool/squid | |
| ``` | |
| The configuration file only allows the proxy to be used for ports 80 | |
| and 443, and disables cache (which would only apply to port 80). | |
| Close the template, you are done with it. | |
| # Setup an out proxy qube | |
| This step could be repeated multiple times, if you want to have | |
| multiple proxies with different lists of domains. | |
| * Create a new qube, let's call it `sys-proxy-out`, based on the | |
| template you configured above (`debian-12-xfce-squid` in the example) | |
| * Configure its firewall to allow the destination `*` and port TCP 443, | |
| and also `*` and port TCP 80 (this covers basic needs for doing | |
| http/https). This is an extra safety to be sure the proxy will not use | |
| another port. | |
| * Start the qube | |
| * Configure the domain list in `/rw/config/domains.txt` with this | |
| format: | |
| ``` | |
| # for a single domain | |
| domain.example | |
| # for all direct subdomains of qubes.org including qubes.org | |
| # this work for doc.qubes-os.org for instance, but not foo.doc.qubes-os.org | |
| .qubes-os.org | |
| ``` | |
| ℹ️ If you change the file, reload with `sudo systemctl reload | |
| squid`. | |
| ℹ️ If you want to check squid started correctly, type `systemctl | |
| status squid`. You should read that it's active, and that there are no | |
| error in the log lines. | |
| ⚠️ If you have a line with a domain included by another line, squid | |
| will not start as it considers it an error! For instance `.qubes.org` | |
| includes `doc.qubes-os.org`. | |
| ⚠️ As far as I know, it is only possible to allow a hostname or a | |
| wildcard of this hostname, so you at least need to know the depth of | |
| the hostname. If you want to allow `anything.anylevel.domain.com`, you | |
| could use `dstdom_regex` instead of `dstdomain`, but it seems a regular | |
| source of configuration problems, and should not be useful for most | |
| users. | |
| In dom0, using the "Qubes Policy Editor" GUI, create a new file named | |
| 50-squid (or edit the file `/etc/qubes/policy.d/50-squid.policy`) and | |
| append the configuration lines that you need to adapt from the | |
| following example: | |
| ``` | |
| qubes.ConnectTCP +3128 MyQube @default allow target=sys-proxy-out | |
| qubes.ConnectTCP +3128 MyQube2 @default allow target=sys-proxy-out | |
| ``` | |
| This will allow qubes `MyQube` and `MyQube2` to use the proxy from | |
| `sys-proxy-out`. Adapt to your needs here. | |
| # How to use the proxy | |
| Now the proxy is set up, and `MyQube` is allowed to use it, a few more | |
| things are required: | |
| * Start qube `MyQube` | |
| * Edit `/rw/config/rc.local` to add `qvm-connect-tcp ::3128` | |
| * Configure http(s) clients to use `localhost:3128` as a proxy | |
| It's possible to define the proxy user wide, this should be picked by | |
| all running programs, using this: | |
| ``` | |
| mkdir -p /home/user/.config/environment.d/ | |
| cat <<EOF >/home/user/.config/environment.d/proxy.conf | |
| all_proxy=http://127.0.0.1:3128/ | |
| EOF | |
| ``` | |
| # Going further | |
| ## Using a disposable qube for the proxy | |
| The sys-proxy-out could be a disposable. In order to proceed: | |
| * mark sys-proxy-out as a disposable template in its settings | |
| * create a new disposable qube using sys-proxy-out as a template | |
| * adapt the dom0 rule to have the new disposable qube name in the | |
| target field | |
| ## Checking logs | |
| In the proxy qube, you can check all requests done in | |
| `/var/log/squid/access.log`, you can filter with `grep TCP_DENIED` to | |
| see denied requests, this can be useful to adapt the domain list. | |
| ## Test the proxy | |
| ### Check allowed domains are reachable | |
| From the http(s) client qube, you can try this command to see if the | |
| proxy is working: | |
| ``` | |
| curl -x http://localhost:3128 https://a_domain_you_allowed/ | |
| ``` | |
| If the output is not `curl: (56) CONNECT tunnel failed, response 403` | |
| then it's working. | |
| ### Check non-allowed domains are denied | |
| Use the same command as above, but with a domain you did not allow | |
| ``` | |
| curl -x http://localhost:3128 https://a_domain_you_allowed/ | |
| ``` | |
| The output should be `curl: (56) CONNECT tunnel failed, response 403`. | |
| ### Verify nothing is getting cached | |
| In the qube `sys-proxy-out`, inspect `/var/spool/squid/`, it should be | |
| empty. If not, please report here, this should not happen. | |
| Some logs file exist in `/var/log/squid/`, if you don't want any hints | |
| about queried domains, configure squid accordingly. Privacy-specific | |
| tweaks are beyond the scope of this guide. |