How to configure the retention time of job?

Tags: #<Tag:0x00007f75963a5288>


Hi Sergey,

May I know if it’s possbile to make the retention time longer? seem like the current job retention time is about 1 day, it would be great if we can configure it to a longer period say a week? coz, it’s more practical in production environment that we need to keep track the job status in longer time period, particularly important when investigating the failed job.

Thanks again for bring this great project to us!


Configure ExpireAt
Keep history of jobs executed for more than 1 day in Hangfire
How can we modify the batches expiration time?

First of all, retention, or expiration time is setting up only for successful and deleted jobs. Other job states, including the failed state, don’t have any expiration time. That is why your failed jobs will be kept infinitely before you fix/retry or delete them manually.

But if you really want to set up retention time for succeeded or deleted jobs, you can add the following job filter (they are like action filters in ASP.NET MVC):

public class ProlongExpirationTimeAttribute : JobFilterAttribute, IApplyStateFilter
    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
        filterContext.JobExpirationTimeout = TimeSpan.FromDays(7);

And apply this filter globally…

GlobalJobFilters.Filters.Add(new ProlongExpirationTimeAttribute());

… or locally, by decorating the method or its class:

public void SomeMethod()
    // Implementation

Succeeded jobs deleted
How to clear succeeded jobs visible on dashboard?
Finished job removal
SQL Server Maintenance
Succeeded Job Queue is not being cleared
Hangfire Dashboard not reporting correct number of succeeded jobs

Thanks for this, Sergey!

Is there any problem leaving the OnStateUnapplied method like this? I don’t have an implementation yet…

public void OnStateUnapplied(ApplyStateContext context, Hangfire.Storage.IWriteOnlyTransaction transaction)
    throw new NotImplementedException();



You are welcome! In this case every background job will end up with an exception, I doubt you want it.


Hope will write more about extensibility in the near future.


Heh, nope don’t want that! I’ll just leave the method empty for now.

Thanks for the quick reply :smile:


How can i apply this attribute using Web Application?

Thank you


The @odinserj helped me.

I solved my issue, I did some changes into the class

public class ProlongExpirationTimeAttribute : JobFilterAttribute, IApplyStateFilter
    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
        context.JobExpirationTimeout = TimeSpan.FromDays(7);

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
        context.JobExpirationTimeout = TimeSpan.FromDays(7);

Now, my job retention time is 7 days.

Thank you so much.


@odinserj When does the expiration manager kick in? is it based on an interval or is it only at application startup?


If you look at the SQLServerStorage class in GitHub you can see the default settings.

public SqlServerStorageOptions()
	TransactionIsolationLevel = null;
	QueuePollInterval = TimeSpan.FromSeconds(15);
	InvisibilityTimeout = TimeSpan.FromMinutes(30);
	JobExpirationCheckInterval = TimeSpan.FromHours(1);
	CountersAggregateInterval = TimeSpan.FromMinutes(5);
	PrepareSchemaIfNecessary = true;
	DashboardJobListLimit = 50000;
	_schemaName = Constants.DefaultSchema;
	TransactionTimeout = TimeSpan.FromMinutes(1);

For one of my projects I wanted to expire some of my manually enqueued jobs after 1 minute. To override the above default settings I used the following in my Hangfire initialization code.

new Hangfire.SqlServer.SqlServerStorageOptions 
	JobExpirationCheckInterval = TimeSpan.FromMinutes(1)


Hey, did you get an answer to this, I am interested too.


@odinserj Does this need to be applied at the server only or both client and server? I get confused sometimes as to which methods, properties, or settings should be invoked on the client vs the server since some are set through Global configuration and some are set through the Owin or .NetCore extension methods.

Sometimes documentations specify which but I wasn’t sure if there’s like a common rule.

Thanks for your help!


I’m trying to implement this, I have the following
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
context.JobExpirationTimeout = TimeSpan.FromSeconds(10);
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
context.JobExpirationTimeout = TimeSpan.FromSeconds(10);

But it only erases the tasks of the succedded page if I restart The process, any idea? thanks


Job expiration runs every 30 minutes by default (for SqlServerStorage, others may vary). So your jobs theoretically should expire in 10 seconds, but it will be actually done only half an hour later.

You may configure JobExpirationCheckInterval in storage options. But running it too often may create a lot of load on storage database, so beware.


This is one of the things I love about Mongo (though I am not sure come to think of it if Sergey uses it in the Mongo storage plugin for Hangfire), TTL indexes.

You can literally create an index on a collection (table for you SQL lubbers :slight_smile: …) that will do server side expiration of documents (records for you SQL lubbers :wink: …)

See Mongo Docs wiki


(edit - solved, see below)

I’m trying to use this attribute at the method level with v1.6.20, but it doesn’t seem to be honoured.
Can anyone see something I’m doing wrong, or has anyone else seen the same?

Per I’ve added the attribute against the method being called, rather than the caller.

public class BackOfficeBusinessService : IBackOfficeBusinessService
    [HangfireExpirationTime(hours: 2)]
    public async Task UserLoginJobs(int userId)
       // do stuff

Which is called by the login controller:

public async Task<IHttpActionResult> Login(LoginCommand command)
    var response = await _loginService.Login(command);
    Hangfire.BackgroundJob.Enqueue<IBackOfficeBusinessService>(service =>
    return Ok(response);

Solution ----------------------------
Per, as I am using DI, putting the attribute on the interface method works instead:

public interface IBackOfficeBusinessService
    [HangfireExpirationTime(hours: 2)]
    Task UserLoginJobs(int userId);