Deployment guide

Lookyloo

Overview

This guide shows you how to deploy and use Lookyloo on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. Lookyloo is a web page capture and investigation tool built by CIRCL, the Computer Incident Response Center Luxembourg. CERTs and security teams use it to triage phishing and malicious sites.

You give Lookyloo a URL. It visits the page with a genuine headless browser and records the complete journey. It then renders the result as an interactive tree: the redirect chain, every hostname and resource the page pulled in, the cookies it set, the scripts it ran, and a screenshot of what a victim would actually see.

The point of the tree is attribution. An analyst can see at a glance which third party actually delivers a payload, where a form would post credentials, or how a redirector hops between disposable domains. They never have to touch the suspicious site from their own workstation.

The image installs Lookyloo v1.40.1, cloned from the upstream repository at that exact tag and verified during the build. It also installs Valkey 8.1.9, compiled from source exactly as the vendor's install guide specifies, and the Playwright browsers that the capture engine drives. The full REST API is enabled and self-documented on the instance.

Security by design: internal addresses cannot be captured. A capture tool that fetches any URL it is given is potentially a probe into your own network. This image keeps upstream's only_global_lookups protection enabled, so capture requests for private and internal address space, including the Azure instance metadata service, are refused. The build verifies this behaviour functionally before the image is published.

Security by design: no default password. In the shipped image, the admin login does not exist at all. Lookyloo's user table is empty, so there is nothing to log in to and nothing to guess. At first boot, your instance generates a 32-character random password for itself and writes the admin user into the configuration. It then proves that the new credential authenticates and that a wrong password is rejected, before first boot is allowed to finish.

What you get:

  • Lookyloo v1.40.1 (pinned upstream tag, BSD 3-Clause licence), run under systemd as lookyloo.service
  • The embedded LacusCore capture engine driving headless Playwright browsers (Chromium, Firefox and WebKit, installed per the vendor's procedure)
  • Valkey 8.1.9 compiled from source (upstream refuses the older Redis 7 that Ubuntu packages), running two instances on local unix sockets for the capture cache and the index
  • nginx on port 80, reverse proxying the Lookyloo website, which itself stays bound to loopback, matching the upstream project's own production layout
  • The REST API with interactive documentation at /doc/, plus compatibility with the pylookyloo client
  • A per-instance admin credential generated at first boot and stored root-only at /root/lookyloo-credentials.txt
  • Unattended security upgrades left enabled, so the underlying OS keeps receiving patches

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
  • A subscription to the Lookyloo listing on Azure Marketplace
  • Network Security Group rules that allow TCP 22 for administration and TCP 80 for the web interface, ideally restricted to your team's source addresses

Recommended virtual machine size: Standard_B2ms (2 vCPU, 8 GB RAM). Treat this as a minimum, not a suggestion. Lookyloo runs its website with ten worker processes plus six background daemons, two Valkey databases and a full headless browser per in-flight capture. The stack uses well over 4 GB under real use, and on a 4 GB instance captures can fail with out-of-memory errors. For heavy multi-analyst use, choose Standard_D4s_v5 or larger.

Restrict who can reach port 80. Anyone who can load the page can submit captures and browse the public capture index. This is how the tool is designed for a team. Scope the NSG rule for port 80 to your organisation's address ranges rather than the whole internet.

1

Deploy from the Azure Portal

Search the Marketplace for Lookyloo, select the SecureAnyCloud publisher and click Create.

Configure the Network Security Group to allow TCP 22 and TCP 80, restricted to your team's source addresses.

2

Deploy from the Azure CLI

Create the resource group, accept the Marketplace terms and create the VM:

RG="soc-tools"; LOCATION="eastus"; VM_NAME="lookyloo1" az group create --name "$RG" --location "$LOCATION" az vm image terms accept --urn sac1647283583153:lookyloo:default:latest az vm create \
  --resource-group "$RG" \
  --name "$VM_NAME" \
  --image sac1647283583153:lookyloo:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard \
  --location "$LOCATION"
# Open the web interface to YOUR source ranges - not the whole internet:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
3

First boot

On first boot, the instance generates its own admin password, writes it into the Lookyloo configuration and restarts the stack. It then verifies that the new credential authenticates and that a wrong password is rejected, before it finishes.

Allow two to three minutes for the full stack to come up the first time. Connect over SSH and read the details, which are unique to your instance:

sudo cat /root/lookyloo-credentials.txt

The file records the web address, the generated admin username and password, and how to obtain an API token. Only root can read it.

4

Confirm the stack is running

Check that both services are active:

systemctl is-active lookyloo.service nginx.service

Both must report active. Then confirm the installed versions and that nginx is the only public HTTP listener:

sudo -u lookyloo git -C /opt/lookyloo/lookyloo describe --tags /opt/lookyloo/valkey/src/valkey-server -v sudo ss -tlnp | grep -E ':80 |:5100 '

The website itself listens on 127.0.0.1:5100, and nginx proxies it on port 80. Both services are enabled and start automatically on every boot:

systemctl is-enabled lookyloo.service nginx.service
5

Capture your first page

Browse to http://<LOOKYLOO_HOST>/ to reach the capture form. Paste a URL, choose the visibility level and submit. The capture runs in the background with a real headless browser. A typical page completes in well under a minute.

When the capture completes, Lookyloo opens the tree view. This is the heart of the product. The captured page is on the left, and every hostname it pulled content from branches to the right. Icons show what each host delivered (JavaScript, cookies received or read, redirects, iframes, fonts, images), and a screenshot shows the rendered page.

Click any node to inspect the URLs it served. Or use the Analytical Tools menu to pivot the whole capture by IP addresses, hostnames, URLs, favicons or fuzzy hashes, and to consult third-party reports on what you captured.

6

Sign in as the admin user

Sign in at http://<LOOKYLOO_HOST>/login with the username and password from /root/lookyloo-credentials.txt.

Authenticated analysts can see hidden captures, rescan and compare captures, and use the admin features. Anonymous visitors see only the public index.

7

Use the REST API

The API is self-documented at http://<LOOKYLOO_HOST>/doc/, and everything the web interface does is available programmatically. Working on the instance itself, obtain your API token with the per-instance credential:

curl -s -X POST http://127.0.0.1:5100/json/get_token \
  -H 'Content-Type: application/json' \
  -d '{"username":"<LOOKYLOO_USER>","password":"<LOOKYLOO_PASSWORD>"}'

The API rejects a wrong password with HTTP 401. You can verify this without exposing anything:

curl -s -o /dev/null -w 'wrong password: HTTP %{http_code}\n' \
  -X POST http://127.0.0.1:5100/json/get_token \
  -H 'Content-Type: application/json' \
  -d '{"username":"<LOOKYLOO_USER>","password":"not-the-password"}'

Submit a capture, wait for it to complete, then retrieve the redirect chain and the rendered screenshot. The submit call returns the capture's UUID, and status_code reaches 1 when the tree is ready:

CAPTURE_UUID=$(curl -s -X POST http://127.0.0.1:5100/submit \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://www.example.com", "listing": true}' | tr -d '"')
echo "capture: $CAPTURE_UUID" for i in $(seq 1 100); do
STATUS=$(curl -s http://127.0.0.1:5100/json/$CAPTURE_UUID/status | jq -r .status_code) [ "$STATUS" = "1" ] && break sleep 3 done echo "final status_code: $STATUS" curl -s http://127.0.0.1:5100/json/$CAPTURE_UUID/redirects curl -s -o /tmp/capture-screenshot.png http://127.0.0.1:5100/bin/$CAPTURE_UUID/screenshot file /tmp/capture-screenshot.png

From your own tooling, use the same endpoints against http://<LOOKYLOO_HOST>/, or install the upstream client with pip install pylookyloo.

8

Verify internal addresses cannot be captured

The image keeps upstream's only_global_lookups protection enabled, which refuses captures of private and internal address space. Submitting the Azure metadata endpoint proves it, because the capture errors instead of fetching. Check the setting:

grep -o '"only_global_lookups": true' /opt/lookyloo/lookyloo/config/generic.json

Leave this set to true on any instance that people other than you can reach. It is what stops the appliance being used to probe your VNet.

Server components

Installed components, versions and purposes:

Lookyloo v1.40.1 (pinned tag): capture and investigation platform

LacusCore + Playwright (bundled with the release): headless browser capture engine (Chromium, Firefox, WebKit)

Valkey 8.1.9 (compiled from source): capture cache and index, unix sockets only

nginx (Ubuntu 24.04 archive): public reverse proxy on port 80

Python 3.12 (Ubuntu 24.04): application runtime, Poetry-managed virtualenv

Filesystem layout

Mount points, sizes and descriptions:

/ (29 GB): root filesystem

/boot (881 MB): kernel files

/boot/efi (105 MB): UEFI boot partition (Gen2 Hyper-V)

/mnt (size varies): Azure temporary resource disk

Key directories:

/opt/lookyloo/lookyloo: the application (LOOKYLOO_HOME), including its Poetry virtualenv

/opt/lookyloo/lookyloo/config/generic.json: main configuration, including the users table

/opt/lookyloo/lookyloo/scraped: capture data

/opt/lookyloo/lookyloo/archived_captures: captures archived after the retention window

/opt/lookyloo/valkey: Valkey 8.1.9 built from source

/opt/lookyloo/.cache/ms-playwright: Playwright browser binaries

/root/lookyloo-credentials.txt: per-instance admin credential (root only)

Managing the service

lookyloo.service wraps the vendor's own poetry run start and poetry run stop commands. These manage the Valkey databases, the capture daemons and the website together:

systemctl status --no-pager -l lookyloo.service | head -12

To restart the whole stack after a configuration change, run sudo systemctl restart lookyloo.service. Allow a minute or two for every component to come back.

Application logs live in /opt/lookyloo/lookyloo/logs/, and website logs live in /opt/lookyloo/lookyloo/website/logs/:

ls /opt/lookyloo/lookyloo/logs/

Configuration

All configuration lives in /opt/lookyloo/lookyloo/config/. generic.json controls the website, capture behaviour, retention (archive, in days), the users table and module toggles. modules.json configures optional third-party integrations such as VirusTotal, MISP and Phishtank. These integrations are disabled by default and need your own API keys. After editing, restart the stack.

Two settings deserve care:

only_global_lookups: leave it true unless this instance is fully private and you knowingly need to capture internal addresses.

async_capture_processes: how many captures run concurrently (default 3). Each in-flight capture runs a full headless browser, so raise it only on instances with more memory.

Troubleshooting

The website answers with 502 from nginx

The stack is still starting, or the website process stopped. Check systemctl status lookyloo.service and the most recent file in /opt/lookyloo/lookyloo/logs/. Restarting lookyloo.service brings the whole stack back in order.

A capture stays queued and never completes

Check memory first with free -m. A full headless browser needs headroom, and an undersized instance is the most common cause of failed or stuck captures. Then check the capture daemon's log under /opt/lookyloo/lookyloo/logs/.

A capture of an internal URL fails with an error

This is by design. only_global_lookups refuses private and internal address space. See Step 8.

I lost the admin password

Generate a new password and write it into the users table in /opt/lookyloo/lookyloo/config/generic.json. The value is stored as the plain password and hashed at load. Update /root/lookyloo-credentials.txt to match, then restart lookyloo.service.

Security recommendations

  • Scope the NSG rule for port 80 to your organisation's source ranges. An open Lookyloo captures pages for anyone who finds it.
  • Leave only_global_lookups set to true so that the instance cannot probe internal address space.
  • Restrict SSH (TCP 22) to known administrative addresses.
  • Leave unattended security upgrades enabled so that the underlying OS stays patched.
  • Treat capture data as sensitive. It contains everything the captured pages served, including any malicious content, and /opt/lookyloo/lookyloo/scraped grows with use.

Support and licensing

Lookyloo is open source software licensed under the BSD 3-Clause licence, copyright CIRCL, the Computer Incident Response Center Luxembourg. The licence text is included on the image at /usr/share/doc/sac/lookyloo/LICENSE.

SecureAnyCloud provides the packaged, hardened Azure image and 24/7 support for the image itself. SecureAnyCloud is not affiliated with or endorsed by CIRCL or the Lookyloo project. Lookyloo is the name of the upstream project and is used here only to identify the software this image packages.

Deploy on Azure

Find Lookyloo on Ubuntu 24.04 LTS by SecureAnyCloud on the Azure Marketplace.

Need Help?

Contact SecureAnyCloud support at sales@secureanycloud.com.

Deploy on Azure

Launch Lookyloo Web Capture & Investigation on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure