Minimal UI image stream: every page load returns one random photo, rendered inside a bare HTML shell with no text chrome. The image response is uncached so it changes on every refresh.
October 2025: The root path now serves a lightweight HTML wrapper that embeds Google Analytics property
G-4CHJNC8WWB, displays the random image inside an<img>tag, and still sources content from nginxrandom_index. Direct links under/photos/…continue to return raw images.
Live
Overview
- Purpose: Serve a different image on each request for playful “this person/place does not exist” vibes, wallpapers, or inspiration.
- UX: Hitting
/serves the minimal HTML shell, which immediately loads/photos/?v=<timestamp>to fetch a random file; deep links under/photos/<filename>serve that exact file. - Simplicity: No app server — implemented directly in nginx via
random_index.
How It Works
- Incoming
GET /servesindex.html, an ultra-minimal page that immediately requests/photos/?v=<timestamp>inside an<img>element. - nginx
location = /photos/enablesrandom_index on;which selects a random file within the directory and returns its contents. - Caching is turned off (
Cache-Control: no-store) to prevent browsers/CDNs from reusing the same image.
Directory Layout
/var/www/apps/thistouristdoesnotexist/
├─ photos/ # Put your images here (the random pool)
└─ analytics/ # Optional: static HTML analytics reports (GoAccess)
Add/Manage Photos
- Upload path:
/var/www/apps/thistouristdoesnotexist/photos/ - Methods: SFTP,
scp, orrsyncfrom your machine.- Example (rsync):
rsync -avz ./my-photos/ root@<droplet>:/var/www/apps/thistouristdoesnotexist/photos/
- Example (rsync):
- Supported formats:
.jpg,.jpeg,.png,.gif,.webp,.svg. - Permissions: typical
0644(rw‑r‑‑r‑‑); directory owned by root and readable by nginx. - Live updates: adding/removing files takes effect immediately; no reload needed.
Content Tips
- Keep only image files in
photos/— random_index returns any file in the folder. - Prefer reasonably sized files (e.g., ≤ 2–4 MB). For faster loads on mobile, consider ≤ 1 MB.
- Optimize images (lossy JPEG/WebP) and strip EXIF when privacy matters.
- Name files however you like; order doesn’t affect selection.
Nginx Config (Installed)
server {
listen 443 ssl; listen [::]:443 ssl;
server_name thistouristdoesnotexist.com www.thistouristdoesnotexist.com;
# Serve analytics if present
location ^~ /analytics/ {
root /var/www/apps/thistouristdoesnotexist;
try_files $uri $uri/ =404;
}
# / -> /photos/ (internal)
rewrite ^/$ /photos/ last;
# Return a random file from photos (no caching)
location = /photos/ {
root /var/www/apps/thistouristdoesnotexist;
random_index on;
expires off;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
}
# Direct file access (no listing; no caching)
location ^~ /photos/ {
root /var/www/apps/thistouristdoesnotexist;
autoindex off;
try_files $uri =404;
expires off;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
}
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy no-referrer-when-downgrade;
}
server {
listen 80; listen [::]:80;
server_name thistouristdoesnotexist.com www.thistouristdoesnotexist.com;
return 301 https://$host$request_uri;
}
HTTPS
- Let’s Encrypt via Certbot is enabled for apex + www; HTTP redirects to HTTPS.
- Renewal test:
sudo certbot renew --dry-run
Analytics & Logs
- Per‑site access log:
/var/log/nginx/thistouristdoesnotexist.com.access.log(if configured). - Static analytics (optional):
/var/www/apps/thistouristdoesnotexist/analytics/index.html.- Generated by cron every 5 minutes via
/usr/local/bin/update-goaccess-reportsand GoAccess.
- Generated by cron every 5 minutes via
- Runtime logs:
/var/log/nginx/access.log,/var/log/nginx/error.log.
Runbook
- Add new photos:
- Upload files into
photos/via SFTP/rsync. - Verify:
curl -I https://thistouristdoesnotexist.com/(check200andContent-Type: image/*).
- Upload files into
- Remove a photo:
- Delete from
photos/; randomness updates immediately.
- Delete from
- Validate config/reload nginx (if you ever change nginx):
sudo nginx -t && sudo systemctl reload nginx
- Disk usage checks:
du -sh /var/www/apps/thistouristdoesnotexist/photos
- Backups:
tar -C /var/www/apps -czf /root/backup-thistouristdoesnotexist-$(date +%F).tgz thistouristdoesnotexist/photos
Troubleshooting
- Blank/404 on root:
- Ensure at least one file exists in
photos/. - Check:
curl -I https://thistouristdoesnotexist.com/photos/<existing-filename>→200.
- Ensure at least one file exists in
- Same image repeats:
- True randomness can repeat; with few files, repeats are common.
- Also check that no intermediary (browser, proxy, CDN) is caching; headers send
no-store.
- Wrong MIME type:
- nginx infers from extension; verify the file’s extension matches its content.
- Slow loads:
- Optimize or resize large images; consider WebP/AVIF for best size/quality trade‑off.
Local Preview (optional)
- nginx
random_indexis server‑side, but you can quickly preview randomness with a tiny static server:- Node one‑liner (random file):
npx http-server -p 8080 --silent & while true; do sleep 0.1; done(then manually hit random files), or - Minimal script idea: a tiny Node/Express route that picks a random file from a folder and
res.sendFile(...).
- Node one‑liner (random file):
Security & Privacy
- Keep only images in
photos/(no HTML/JS executables). - Strip EXIF if you don’t want to leak GPS/camera metadata.
- Directory listing is disabled; only direct file paths and the random endpoint are served.
Extensibility Ideas
- Weighted randomness (e.g., newer files more likely) — requires a small server shim.
- Category subfolders
/photos/<tag>/with random per tag. - Add a simple HTML wrapper page that centers the image and offers copy/download.
FAQ
- Q: Can I put this behind a CDN?
- A: Yes, but disable caching on
/and/photos/or vary by a cache‑busting header, otherwise randomness will be lost.
- A: Yes, but disable caching on
- Q: Can I guarantee no repeats for N requests?
- A: Not with pure
random_index. Implement a server with a small stateful shuffle queue if you need non‑repeating draws.
- A: Not with pure
License
- Content (images) copyright remains yours. Server config and docs follow the repository’s license.