Safe Way to Delete Umbraco Cloud Media Cache Folder

Hello everyone,

We’re working on an Umbraco Cloud project and noticed that the cache folder inside the media blob storage has grown quite large.

Before removing anything, I wanted to confirm:

  • Is it safe to delete the cache folder directly using the Azure Storage Explorer?

  • After deleting the cache folder, is it also necessary to purge the Umbraco Cloud CDN cache so the site doesn’t serve stale media variants?

  • Is there an official method or best practice for clearing media cache in Cloud environments?

Just want to make sure this cleanup is done properly.

Kind regards

Nathaniel Nunes

CC @ab-umbraco-cloud

Hi @manutdkid77

It is safe to delete the cache folder using Azure Storage Explorer as it can get quite large over time, I have done this before on Umbraco Cloud sites and never had an issue.

If you also purge the CDN cache, it will re-fetch and re-process every image you display which could slow page speed times down initially whilst images are regenerated and cached. It wouldn’t hurt to do this but you could leave it unless you feel it is also necessary.

Hope that helps!

Justin

Thank you @justin-nevitech for confirming this and also for the tip on clearing the CDN cache.

Nathaniel

A few ideas for active maintenance

A package that has configurable ImageSharp cache-retention cleanup???
AF Umbraco Azure Blob Media Storage | Umbraco Marketplace

Not sure if this is possible as it’s Umbraco cloud..
But you can do some maintenance on the cache folder by adding a Data Management LifeCycle rule to the blob storage.. (a filter can limit to cache only)

Might also be a lastAccessed that you can add that might be better for these unused cache files which are morelikely to be the ones you want.

However, in Azure Blob Storage (2026), there is a specific feature called Access Time Tracking. It is not enabled by default because it adds a tiny transaction cost, but it is exactly what you need to solve the “Kosher vs. Orphan” problem.

How to find the “Last Accessed” option

If you don’t see it in your Lifecycle Management rules right now, it’s because the tracking is likely turned off at the Storage Account level.

  1. Go to your Azure Storage Account.
  2. On the left menu, under Data Management, select Lifecycle Management.
  3. Look for a checkbox or a tab that says “Enable access tracking”.
  4. Once checked and saved, a new property called LastAccessTime will be recorded for every blob (it updates at most once every 24 hours to keep costs low).

Setting up the “Smart” Cleanup Rule

Once tracking is on, you can go to Add a Rule and you will see a new dropdown in the Actions tab:

  • Blob type: Base blobs
  • Subtype: Current version
  • Condition: Days after last access
  • Value: 30 (or 60)
  • Action: Delete

Why this is the “Gold Standard” for ImageSharp

This configuration is the perfect balance between your surgical plan and the “sledgehammer”:

  1. Protects “Kosher” Files: If an image is on your homepage and gets 1,000 views a day, its LastAccessTime is always “Today.” It will never be deleted.
  2. Kills “Orphans”: If you delete a media item in Umbraco, no one can ever request that URL again. The cached file will sit untouched. After 30 days of zero views, Azure will see it hasn’t been “Accessed” and delete it automatically.
  3. Zero Maintenance: You don’t have to write a single line of C# or maintain an Examine index to find crops.

A quick cost note

Azure charges for “Access Tracking” as an “Other Transaction.” For a typical Umbraco site, this cost is negligible (pennies per month), but it is significantly cheaper than the developer time required to build and debug a custom surgical cleanup handler.

More of a thought exercise on the Umbraco Side of the fence than a recommendation :face_with_peeking_eye:

.. a surgical strike on MediaDeletedNotification (or could be MediaEmptiedRecycleBinNotification) … use examine to find where media existed, get it’s cropurls from local/global crops. get the hash used for the cache filename and try and delete it… with the caveat that manually set crops in code will be missed..

AI says…

using Examine;
using SixLabors.ImageSharp.Web.Caching;
using SixLabors.ImageSharp.Web.Commands;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;

namespace MyProject.Handlers;

public class MediaSecurityCleanupHandler : INotificationHandler<MediaDeletedNotification>
{
    private readonly IExamineManager _examineManager;
    private readonly IUmbracoContextFactory _umbracoContextFactory;
    private readonly MediaFileManager _mediaFileManager;
    private readonly ICacheKey _cacheKeyGenerator;
    private readonly ICacheHash _cacheHash;

    public MediaSecurityCleanupHandler(
        IExamineManager examineManager,
        IUmbracoContextFactory umbracoContextFactory,
        MediaFileManager mediaFileManager,
        ICacheKey cacheKeyGenerator,
        ICacheHash cacheHash)
    {
        _examineManager = examineManager;
        _umbracoContextFactory = umbracoContextFactory;
        _mediaFileManager = mediaFileManager;
        _cacheKeyGenerator = cacheKeyGenerator;
        _cacheHash = cacheHash;
    }

    public void Handle(MediaDeletedNotification notification)
    {
        using var contextReference = _umbracoContextFactory.EnsureUmbracoContext();
        var snapshot = contextReference.UmbracoContext.PublishedSnapshot;

        foreach (var media in notification.DeletedEntities)
        {
            var udi = media.GetUdi().ToString();

            if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) continue;

            // Find every content node using this image - could be tailored to known properties?
            var results = index.Searcher.CreateQuery("content")
                .NativeQuery($"*:*{udi}*")
                .Execute();

            foreach (var result in results)
            {
                var content = snapshot.Content.GetById(int.Parse(result.Id));
                if (content == null) continue;

                var picker = content.Value<MediaWithCrops>("mediaPickerAlias");
                if (picker == null || picker.Content.Key != media.Key) continue;

                // 1. Delete Local Crops
                if (picker.LocalCrops?.Crops != null)
                {
                    foreach (var crop in picker.LocalCrops.Crops)
                    {
                        var cropUrl = picker.GetCropUrl(crop.Alias);
                        DeleteImageSharpCacheFile(cropUrl);
                    }
                }
            }

            // 2. Delete the original path (un-cropped cached version)
            var mediaPath = media.GetValue<string>(Constants.Conventions.Media.File);
            if (!string.IsNullOrEmpty(mediaPath))
            {
                DeleteImageSharpCacheFile(mediaPath);
            }
        }
    }

    private void DeleteImageSharpCacheFile(string url)
    {
        if (string.IsNullOrEmpty(url)) return;

        // Parse commands from URL
        var parts = url.Split('?');
        var query = parts.Length > 1 ? parts[1] : string.Empty;
        var commands = new CommandCollection();
        var dict = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(query);
        foreach (var kvp in dict) commands.Add(kvp.Key, kvp.Value.ToString());

        // Generate the Hash (Filename)
        // ImageSharp uses a 12-char hash by default in Umbraco
        string key = _cacheKeyGenerator.Create(null, commands);
        string hashName = _cacheHash.Create(key, 12);

        // Construct the path relative to the root of the Media FileSystem
        // Based on your setup: no nesting, just the /cache/ folder
        var cacheFilePath = $"cache/{hashName}";

        if (_mediaFileManager.FileSystem.FileExists(cacheFilePath))
        {
            _mediaFileManager.FileSystem.DeleteFile(cacheFilePath);
        }
    }
}
public class MediaSecurityCleanupHandlerComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddNotificationHandler<MediaDeletedNotification, MediaSecurityCleanupHandler >();
    }
}

Or on the azure side..

cache files do have metadata..

Using an Azure Function (Timer Trigger) against the Meta might be much more robust “Background Worker” than trying to run this inside Umbraco’s process.

or…

Instead of one /cache/ folder, you configure ImageSharp to save to /cache/2026-03/.

  • Next month, you update your appsettings.json to /cache/2026-04/.
  • You then simply Delete the entire 2026-03 folder in one single command after a couple of days overlap.
  • This is 1,000x faster and cheaper than checking metadata on individual files.

Unless I am mistaken you can do this directly in Cloud here: https://www.s1.umbraco.io/project/{PROJECT_NAME}/cdn-caching

Thank you @mistyn8 for investigating & sharing this option as well. Have created a Feature Request for Umbraco Cloud

@JamieT I think that clears just the Umbrao Cloud (cloudflare) CDN cache.

Connecting to Azure Storage Explorer for the respective environment and clearing the content of the cache folder solved it for us :grinning_face:

Additionally also cleared the CDN cache as suggested by @justin-nevitech

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.