How to Fix Images Not Displaying in Rotativa and wkhtmltopdf
Fix missing images in Rotativa and wkhtmltopdf by checking URLs, base paths, local-file access, permissions, networking, and render timing.

Direct answer: Images disappear in Rotativa and wkhtmltopdf when the converter cannot resolve the image URL, is prevented from reading a local file, cannot reach a remote host, has image loading disabled, or prints before JavaScript finishes creating the image. Inspect the exact HTML and converter arguments, then test the image from the same process, container, and user identity that runs wkhtmltopdf.
Use this order: verify the src, verify Rotativa’s base URL, confirm image loading, separate remote URLs from local files, allow only the required local directory, check permissions and AppArmor, then investigate JavaScript timing and media-load logs.
1. Identify what kind of image reference is failing
Capture or inspect the HTML that Rotativa passes to wkhtmltopdf. Classify every failing <img src> as one of these:
| Reference | What to verify |
|---|---|
Relative URL, such as /images/logo.png or images/logo.png |
The document’s effective base URL and the final URL produced from it |
| Absolute HTTP(S) URL | DNS, TLS, authentication, request headers, firewall rules, and access from the converter host |
Local path or file:// URL |
File existence, path syntax, process permissions, local-file policy, and container mounts |
| JavaScript-generated URL | Whether the image element and final src exist before printing |
A path that works in your browser or ASP.NET web process may fail in a separate worker, container, or host. Always test from the renderer’s actual execution context.
2. Check Rotativa’s base URL
Rotativa.AspNetCore injects a base URL into the document head by default. That base controls how relative image paths resolve. See the Rotativa.AspNetCore README.

public IActionResult Invoice(int id)
{
var model = repository.GetInvoice(id);
return new ViewAsPdf("Invoice", model)
{
FileName = $"invoice-{id}.pdf"
};
}
Inspect the generated HTML and its <base href="...">. If the base points at the wrong host, scheme, port, or application path, a relative reference such as images/logo.png will resolve incorrectly.
For a partial view whose URLs must remain unchanged, Rotativa supports disabling base injection:
return new ViewAsPdf(
"Invoice",
isPartialView: true,
viewData: ViewData,
setBaseUrl: false
);
Disabling the base does not repair paths by itself. Use resolvable absolute URLs or provide the correct base in the HTML.
3. Verify image loading and the effective wkhtmltopdf arguments
wkhtmltopdf documents image loading as enabled by default; --no-images disables it. Rotativa wrappers, custom switches, package versions, and distribution builds can change the effective command, so inspect what is actually executed. The library setting is commonly exposed as web.loadImages. Refer to the wkhtmltopdf usage documentation and libwkhtmltox page settings.
var pdf = new ViewAsPdf("Invoice", model)
{
CustomSwitches = "--enable-javascript --javascript-delay 500"
};
return pdf;
Do not assume a setting from another operating system package applies to your executable. Run the deployed binary directly:
wkhtmltopdf --version
wkhtmltopdf --extended-help | grep -E -- "--(no-images|enable-local-file-access|disable-local-file-access|allow|javascript-delay|debug-javascript)"
Remove --no-images if it is present. If you use the library API, confirm that the image setting is enabled in the generated command or equivalent settings object.
4. Fix relative and absolute HTTP(S) image URLs
Relative paths
Prefer a URL that resolves from the rendered document. For example:
<img src="/images/company-mark.png" alt="Company mark">
Then confirm that the base URL’s origin serves /images/company-mark.png. If the application is mounted below a path, use the correct application-relative URL or an absolute URL generated for the public origin.
Remote images
From the machine or container that runs conversion, request the exact image URL:
curl -I -L --max-time 20 https://example.com/images/company-mark.png
Check DNS resolution, TLS negotiation, redirects, authentication, required headers, and outbound firewall rules. If the image requires a cookie or authorization header, configure the converter or make the asset available through a URL it can access. A successful request from your laptop does not prove that the renderer host can reach it.
5. Enable only the local-file access you need
Local images are controlled by wkhtmltopdf’s local-file policy. The documented controls include --disable-local-file-access, --enable-local-file-access, and the narrower --allow directory option.
For a trusted, known asset directory, prefer a narrow allow-list:
wkhtmltopdf \
--allow /srv/myapp/wwwroot/images \
/srv/myapp/render/invoice.html \
/srv/myapp/render/invoice.pdf
If your deployed build requires broad local access, the equivalent switch is:
wkhtmltopdf --enable-local-file-access input.html output.pdf
Use the smallest directory scope that works. Broad access can expose unrelated files when the HTML is untrusted. The exact defaults and option support depend on the installed build, so check that executable’s help output and the arguments Rotativa emits.
Use a correctly formed file URL
<img src="file:///srv/myapp/wwwroot/images/company-mark.png" alt="Company mark">
On Windows, construct a valid file URL rather than concatenating a backslash path into HTML:
var path = Path.GetFullPath("wwwroot/images/company-mark.png");
var fileUrl = new Uri(path).AbsoluteUri;
// fileUrl is suitable for an img src value.
6. Check permissions, containers, and AppArmor
The wkhtmltopdf process must be able to read the file under its real service identity. Check all parent directories as well as the file:
id
namei -l /srv/myapp/wwwroot/images/company-mark.png
stat /srv/myapp/wwwroot/images/company-mark.png
sudo -u www-data test -r /srv/myapp/wwwroot/images/company-mark.png && echo readable
In a container, verify that the image directory is copied into the image or mounted at the same path visible to the converter. A host path is not automatically present inside a container.
On Linux, AppArmor can deny a path even when ordinary Unix permissions allow it. The wkhtmltopdf AppArmor guidance shows how to confine filesystem access and add the working paths required by your profile. Check denial messages in the kernel or system logs and add only the render and asset directories needed by the job.
7. Handle JavaScript-created images and timing
If JavaScript inserts an image or changes its src, inspect the DOM after the page has run. wkhtmltopdf’s documented CLI enables JavaScript by default, and it provides a JavaScript delay and debugging controls. A delay that works for one page is not universal.
wkhtmltopdf \
--enable-javascript \
--javascript-delay 1000 \
--debug-javascript \
https://example.com/report \
report.pdf
Use a deterministic readiness marker where your page can provide one:
window.addEventListener('load', () => {
document.documentElement.dataset.pdfReady = 'true';
});
Then wait for that condition through your application workflow or choose a delay long enough for the slowest required asset. If the image is lazy-loaded, scroll or trigger the page’s loading code before printing.
8. Do not trust a successful PDF exit code alone
A PDF can be produced even when one or more media resources failed. wkhtmltopdf has separate page-load and media-load error handling, and documented defaults can ignore media errors. Enable diagnostic logging and inspect the converter output for missing files, access-denied messages, URL failures, or JavaScript errors.
For difficult cases, save the exact HTML, command line, renderer version, stderr output, and a copy of the failing asset. This makes the problem reproducible outside the web request.
9. Complete Rotativa diagnostic example
public IActionResult Invoice(int id)
{
var model = repository.GetInvoice(id);
var pdf = new ViewAsPdf("Invoice", model)
{
FileName = $"invoice-{id}.pdf",
CustomSwitches = string.Join(" ", new[]
{
"--enable-javascript",
"--javascript-delay 500",
"--allow /srv/myapp/wwwroot/images"
})
};
return pdf;
}
Before using this in production, replace the path with the directory visible to the conversion process, confirm your installed build supports each switch, and remove the delay if the page is already deterministic.
10. Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| All images are missing | --no-images or web.loadImages disabled |
Inspect effective arguments and enable image loading |
| Only relative URLs fail | Wrong or injected base URL | Inspect <base>; correct the origin or use valid absolute URLs |
| Remote images fail but local ones work | DNS, TLS, firewall, authentication, or headers | Run curl from the renderer host and fix network access |
| Local images show “blocked” or “not allowed” | Local-file policy | Use a narrow --allow path or enable local access for trusted input |
| Works locally, fails in production | Different user, container filesystem, host, or AppArmor profile | Test as the service identity in the deployed runtime |
| Images appear intermittently | JavaScript or lazy-loading race | Wait for a readiness condition or tune the JavaScript delay |
| PDF exits successfully but has blanks | Media errors were ignored | Read stderr and media-load logs; validate the output assets |
| Windows local path behaves strangely | Malformed URL or backslash path | Convert the absolute path with new Uri(path).AbsoluteUri |
11. Performance, reliability, and security
- Performance: Remote images add DNS, TLS, and download time. Large images increase memory and PDF size. Reuse stable asset URLs and avoid waiting longer than the page needs.
- Reliability: Pin and record the wkhtmltopdf build, keep the HTML and asset paths consistent across environments, and capture stderr for failed jobs. Treat media validation as a separate check from process exit status.
- Security: Do not render untrusted HTML with broad local-file access. Prefer a narrow
--allowdirectory and confinement such as AppArmor. The wkhtmltopdf project warns against using it with unsanitized untrusted HTML. - Operations: Record the generated command, renderer version, service identity, container mount paths, and failing URLs whenever a production PDF omits an image.
12. Or skip the browser setup
If your goal is a clean capture of a web page rather than maintaining a local wkhtmltopdf pipeline, ScreenshotNeo provides a one-request screenshot or PDF API. See the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients take screenshots with take_screenshot, inspect pages with get_page_info, and create PDFs with capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account.
13. When to ask for more information
No single switch fixes every missing-image case. If the checks above do not identify the cause, collect:
- Rotativa package and wkhtmltopdf version
- Operating system and deployment topology
- The generated HTML and every failing
src - The exact converter arguments
- Service identity, container mounts, and filesystem permissions
- stderr, media-load logs, and any AppArmor denial
With those details, you can distinguish a URL-resolution problem from a policy, network, permission, or timing failure instead of changing unrelated options.
FAQ
Does Rotativa embed images automatically?
No. The renderer still has to resolve and load each image URL or local file from its execution environment.
Should I always enable --enable-local-file-access?
No. Use a narrow --allow directory when possible, especially for HTML that is not fully trusted.
Why does the PDF generate without an error?
Media-load failures can be handled separately from page-load failures and may be ignored. Check stderr and the rendered output.
Is a longer JavaScript delay always better?
No. It can hide a race but increases latency and still may miss slow or failed resources. Use a deterministic readiness signal when possible.
What is the fastest diagnostic test?
Save the exact HTML, run the exact wkhtmltopdf command as the production service identity, and test the failing image URL or file path from that same environment.


