Without pain, without sacrifice, we would have nothing, Chuck Palahniuk - Fight Club.

While Google is familiar to everyone, it doesn’t need to know every detail about you or profit from your personal and even sensitive data. Fortunately, there are alternative search engines that prioritize your privacy and often come with additional benefits like faster results, no ads, or even open-source transparency.
One such example is SearXNG, a free and open-source metasearch engine forked from Searx. It aggregates results from more than 80 different search services and databases, giving users a comprehensive and decentralized search experience. What sets SearXNG apart is its commitment to privacy — users are neither tracked nor profiled, and all searches are done anonymously by default.
Additionally, because it’s open-source, the codebase is publicly auditable, ensuring transparency and security. You can even self-host your own instance if you want full control over your search environment. With features like customizable result categories, multi-engine querying, and optional Tor/VPN integration, SearXNG offers both power and privacy for everyday users and advanced users alike.
This guide will walk you through setting up SearXNG in a privileged Proxmox LXC container (with ZFS storage) using Docker, step by step.
SearXNG doesn’t care about what you search for, never shares anything with a third-party, and it can’t be used to compromise you.
Proxmox VE is a popular virtualization platform for home lab enthusiasts, offering both full VMs and LXC containers. In our case, using an LXC container for SearXNG has the advantage of being lightweight while still isolating the application.
Docker provides the ability to package and run an application in a loosely isolated environment called a container. Essentially, running Docker inside an LXC trades a bit of isolation for efficiency – acceptable for many use cases. The isolation and security allows you to run many containers simultaneously on a given host. Containers are lightweight and contain everything needed to run the application, so you do not need to rely on what is currently installed on the host.
In this section, we’ll create a new LXC container on Proxmox to host our SearXNG instance. We’ll use an Ubuntu template for the container’s OS and the container will be configured with a static IP address, so you can easily access SearXNG via that address.
#!/bin/bash
# Define variables for convenience
CTID=309 # Container ID
OSTEMPLATE="ubuntu-24.10-standard_24.10-1_amd64.tar.zst"
TEMPLATE_STORAGE="local" # Storage for the template
CONTAINER_STORAGE="mypool" # ZFS storage pool for container disks
DISK_SIZE="80" # Disk size in GB
PASSWORD="Anawim" # Root password
HOSTNAME="searx" # Hostname
MEMORY=4096 # Memory in MB (Docker needs more resources)
CORES=2 # Number of CPU cores
BRIDGE="vmbr0"
IPADDRESS="192.168.1.59" # Desired static IP
GATEWAY="192.168.1.1" # Your LAN gateway
CIDR="/24" # Adjust if not 255.255.255.0
PORT=8080
########################################################
# Create the LXC container with the specified settings #
########################################################
# If container $CTID exists, remove it (optional)
if pct status $CTID &>/dev/null; then
echo "Container $CTID exists. Stopping the container."
pct stop $CTID
sleep 2 # Giving it a moment to stop gracefully
echo "Proceeding with deletion of container $CTID."
pct destroy $CTID
else echo "Container $CTID does not exist."
fi
########################################################
# Download template if needed #
########################################################
# Check if the template is already downloaded
if ! pveam list $TEMPLATE_STORAGE | grep -q $OSTEMPLATE; then
echo "Downloading Ubuntu template..."
pveam download $TEMPLATE_STORAGE $OSTEMPLATE
else
echo "Ubuntu template already exists."
fi
# Create a privileged Ubuntu container
# We specified the container ID, template and storage.
# The root filesystem will be an 80 GB volume on the ZFS pool mypool.
# We gave the container a hostname (searx) and allocated 2 CPU cores and 4096 MB RAM.
# We set swap to 0 for simplicity, the container is privileged by default.
# We set a root password and a static IPv4: 192.168.1.59/24 with gateway 192.168.1.1.
# Adjust IP_ADDR, GATEWAY, MEMORY, CORES as needed for your setup.
pct create $CTID $TEMPLATE_STORAGE:vztmpl/$OSTEMPLATE \
--storage $CONTAINER_STORAGE \
--rootfs $CONTAINER_STORAGE:$DISK_SIZE \
--password $PASSWORD \
--hostname $HOSTNAME \
--memory $MEMORY \
--cores $CORES \
--swap 0 \
--net0 name=eth0,bridge=$BRIDGE,ip=$IPADDRESS$CIDR,gw=$GATEWAY \
--unprivileged 0 # Set to 1 for unprivileged containers
# Optional: set the container to auto-start on host boot
pct set $CTID --onboot 0
# ALTERNATIVE: Configure network interface with DHCP
# pct set $CTID --net0 name=eth0,bridge=vmbr0,ip=dhcp,firewall=0
# pct set $CTID --nameserver 8.8.8.8
# Setup a static IP via Cloud-Init (if you want):
# qm set $CTID --ipconfig0 ip=$IPADDRESS$CIDR,gw=$GATEWAY
# 'keyctl=1' and 'mknod=1' enable specific system calls in the container needed by Docker
# Enable Docker nesting features (it allows nested container features -needed for Docker)
pct set $CTID --features nesting=1,keyctl=1,mknod=1
# Unconfine AppArmor
echo "lxc.apparmor.profile: unconfined" >> /etc/pve/lxc/$CTID.conf
# Once created, start the container
pct start $CTID
# Wait a moment for container to boot
sleep 5
########################################################
# Install Docker inside the Ubuntu container #
########################################################
pct exec $CTID -- apt-get update
pct exec $CTID -- apt-get install -y docker.io
# Once this completes, you have Docker installed inside the LXC.
# You can verify by running docker --version and sudo systemctl status docker.
pct exec $CTID -- echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
pct exec $CTID -- echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
# Enable and start Docker
pct exec $CTID -- systemctl enable docker
pct exec $CTID -- systemctl start docker
# Test Docker by running docker run hello-world which should pull a test image and run it.
echo "Test Docker"
pct exec $CTID -- docker run hello-world
# Check IP addresses
echo "Check IP addresses"
pct exec $CTID -- ip a
# Test Docker
echo "Test Docker"
pct exec $CTID -- docker run hello-world
# If you see an IP, test connectivity
pct exec $CTID -- ping -c 3 google.com
########################################################
# Deploying the SearXNG Docker Container #
########################################################
pct exec $CTID -- mkdir -p /etc/local.d
# We can use Docker’s --restart policy to ensure SearXNG starts on boot of the Docker service.
# Since we also set the LXC container to auto-start with the host, in theory everything should come up on a Proxmox reboot.
# However, sometimes you might want a more controlled way to manage the SearXNG container startup inside the LXC.
# Setting Up a Systemd Service for Auto-Start
echo "Creating the startup-script.service"
# Inside the container, create a file at /etc/systemd/system/startup-script.service
pct exec $CTID -- bash -c "cat > /etc/systemd/system/startup-script.service << 'EOF'
[Unit]
Description=SearXNG Docker Container
After=network.target docker.service
# Specifies that this service should start after the network and Docker services are up.
[Service] # Defines how the service behaves.
Type=simple # Indicates that the service is a simple service that runs a command.
ExecStart=/etc/local.d/searx-startup.sh
# The command to execute when starting the service (in this case, it points to a script that starts the SearXNG container).
RemainAfterExit=true
# It tells systemd that the service should be considered "active" even after the main process exits.
Restart=always
# If the SearXNG container stops or crashes, systemd will try to restart it.
RestartSec=10s
# It adds a 10-second delay between restart attempts to avoid a tight loop if something is wrong.
[Install]
WantedBy=multi-user.target
# Specifies the target that this service should be part of, in this case, the multi-user.target, which is the standard run level for most multi-user systems.
EOF"
pct exec $CTID -- bash -c "cat > /etc/local.d/searx-startup.sh << 'EOF'
#!/bin/bash
# Ensure Docker is running
while ! systemctl is-active --quiet docker; do
echo 'Waiting for Docker...'
sleep 1
done
# If container is already running, stop/remove it (optional)
docker rm -f searx_container 2>/dev/null || true
# This will download the official SearXNG Docker image
docker pull searxng/searxng
# Run container -d in detached mode (in the background).
# --name searxng gives the container a friendly and descriptive name.
# -p 8080:8080 maps the container’s internal port 8080 to port 8080 on the container’s network interface.
# Since our LXC has IP 192.168.1.59, this means SearXNG’s web interface will be accessible at http://192.168.1.59:8080 on the LAN.
# --restart=unless-stopped is a Docker policy to automatically start the container on boot/restart unless it was manually stopped.
# This is very important for persistence – if the container or Docker daemon restarts, SearXNG will come back online automatically.
# searxng/searxng:latest specifies the image (using the "latest" tag).
docker run -d --name searx_container \
-p ${PORT}:8080 \
-v "${PWD}/searxng:/etc/searxng" \
-e "BASE_URL=http://localhost:$PORT/" \
-e "INSTANCE_NAME=my-instance" \
searxng/searxng
EOF"
pct exec $CTID -- chmod +x /etc/local.d/searx-startup.sh
# After creating this file, reload systemd and enable the service:
pct exec $CTID -- systemctl daemon-reload
pct exec $CTID -- systemctl enable startup-script.service
pct exec $CTID -- systemctl start startup-script.service
# Output success message
echo "Ubuntu-based LXC container $CTID created with Docker installed. https://192.168.1.59:8080/"
echo "Searx is running on port 8080."
# After running this, do "docker ps" to verify the SearXNG container is up.
# You should see a container running, with the PORTS column showing 0.0.0.0:8080->8080/tcp.
# You can also check logs with docker logs searxng.
Your SearXNG instance should now be accessible at the container’s IP and port 8080, [HTTP:] + [//dirIP_Container_Hosting_Searx] : [8080/].
By default, SearXNG’s Docker image will generate a config file (settings.yml) and other files in the container’s /etc/searxng. Because we did not mount a volume for persistence, those files live inside the container. They will persist across restarts of the container, but if you remove the container, you’d lose any custom changes. For a long-term setup, you might consider mapping a container volume. For example, you could create a directory /opt/searxng on the LXC and add -v /opt/searxng:/etc/searxng to the docker run command.
Currently, our SearXNG instance returns HTTP 403 (Forbidden) when we query with format=json (duckduckgo_html_fallback). Multiple sources confirm this is because the JSON output format is disabled by default in SearXNG installations (including Docker images).
On your Proxmox container (-v “${PWD}/searxng:/etc/searxng"), the settings file is found at: ${PWD}/searxng/settings.yml, so we need to edit this file and ensure the following section exists:
nvim /home/nmaximo7/homelab/mydockers/searxng
search:
[...] # If formats is not present, add it under the search: section.
# Indentation in YAML is critical.
# remove format to deny access, use lower case
# formats: html, csv, json, rss
formats:
- html # By default, only "html" is enabled as an output format
# When you attempt /search?format=json with JSON not listed, SearXNG intentionally returns HTTP 403 as a safeguard
- json
Restart SearXNG Docker Container: docker restart searx_container