Summary
On current Herd (1.28.0, macOS), the generated per-site nginx config returns HTTP 404 for /favicon.ico and /robots.txt while still streaming the correct file bytes from the site's public/ directory. Browsers honor the 404 and refuse to render the favicon; other static assets in public/ serve fine (200).
Environment
- Herd 1.28.0
- macOS (Darwin 25.4.0)
- Laravel 11 site, parked normally
- Site doc root via Herd is the project folder; favicon and robots.txt both live in
public/
Reproduction
herd park a Laravel 11 project that has public/favicon.ico and public/robots.txt.
- Visit
https://<site>.test/favicon.ico.
- Observe response: status 404,
content-type: image/x-icon, content-length matching the actual file size.
- Any other static asset in
public/ (e.g. public/curitics.png) returns 200.
Verified with:
curl -sI https://care-management.test/favicon.ico -k
# HTTP/2 404
# content-type: image/x-icon
# content-length: 15086
Root cause
The generated config at ~/Library/Application Support/Herd/config/valet/Nginx/<site>.test contains:
server {
...
root /;
...
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
...
error_page 404 "/Applications/Herd.app/Contents/Resources/valet/server.php";
}
The two location = blocks capture the request but contain no try_files, alias, or fastcgi_pass. With server-level root /, nginx looks for the file at filesystem root, fails, and emits 404. The error_page 404 directive then invokes Valet's server.php, which correctly serves the bytes from the site's public/ folder — but the 404 status is already set and server.php does not override it to 200 for this path, so browsers discard the body.
Other static files in public/ are unaffected because they hit the location / block which rewrites to server.php directly (no prior 404 set).
Workaround
Removing the two empty location = blocks from both server blocks (ports 443 and 60) in the generated config and running herd restart restores 200 responses. This edit is lost on any regeneration.
Suggested fix
Either:
- Drop the empty
location = /favicon.ico / location = /robots.txt blocks from the template — they no longer serve their original purpose (log silencing) because the requests already route through server.php cleanly without them.
- Or point both blocks at
server.php explicitly:
location = /favicon.ico {
access_log off;
log_not_found off;
try_files $uri /Applications/Herd.app/Contents/Resources/valet/server.php;
}
Happy to provide additional diagnostic info.
Summary
On current Herd (1.28.0, macOS), the generated per-site nginx config returns HTTP 404 for
/favicon.icoand/robots.txtwhile still streaming the correct file bytes from the site'spublic/directory. Browsers honor the 404 and refuse to render the favicon; other static assets inpublic/serve fine (200).Environment
public/Reproduction
herd parka Laravel 11 project that haspublic/favicon.icoandpublic/robots.txt.https://<site>.test/favicon.ico.content-type: image/x-icon,content-lengthmatching the actual file size.public/(e.g.public/curitics.png) returns 200.Verified with:
Root cause
The generated config at
~/Library/Application Support/Herd/config/valet/Nginx/<site>.testcontains:The two
location =blocks capture the request but contain notry_files,alias, orfastcgi_pass. With server-levelroot /, nginx looks for the file at filesystem root, fails, and emits 404. Theerror_page 404directive then invokes Valet'sserver.php, which correctly serves the bytes from the site'spublic/folder — but the 404 status is already set andserver.phpdoes not override it to 200 for this path, so browsers discard the body.Other static files in
public/are unaffected because they hit thelocation /block which rewrites toserver.phpdirectly (no prior 404 set).Workaround
Removing the two empty
location =blocks from both server blocks (ports 443 and 60) in the generated config and runningherd restartrestores 200 responses. This edit is lost on any regeneration.Suggested fix
Either:
location = /favicon.ico/location = /robots.txtblocks from the template — they no longer serve their original purpose (log silencing) because the requests already route throughserver.phpcleanly without them.server.phpexplicitly:Happy to provide additional diagnostic info.