# How to configure the retention time of job?

**URL:** <https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34>\
**Category:** question\
**Tags:** job-filters\
**Created:** [May 29, 2014, 4:09am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34 "2014-05-29T04:09:57Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![yinkinkwok](https://avatars.discourse-cdn.com/v4/letter/y/9de0a6/32.png) [@yinkinkwok](https://discuss.hangfire.io/u/yinkinkwok)\
**Post date:** [May 29, 2014, 4:09am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/1 "2014-05-29T04:09:57Z")

</div>

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!

Alex

---

<div class="post-metadata">

**Author:** ![odinserj](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/odinserj/32/648_2.png) [@odinserj](https://discuss.hangfire.io/u/odinserj)\
**Post date:** [May 29, 2014, 4:50pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/2 "2014-05-29T16:50:06Z")

</div>

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](http://ASP.NET) MVC):

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

```

And apply this filter globally…

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

```

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

```auto
[ProlongExpirationTime]
public void SomeMethod()
{
    // Implementation
}

```

In the recent versions of Hangfire it is also possible to specify the expiration globally by using the `WithJobExpirationTimeout` method just after the `UseXXXStorage` (where `XXX` is the storage you are using) and pass the required timeout as an argument.

```auto
Configuration
    .UseXXXStorage()
    .WithJobExpirationTimeout(TimeSpan.FromDays(4))
```

---

<div class="post-metadata">

**Author:** ![mynameistodd](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/mynameistodd/32/60_2.png) [@mynameistodd](https://discuss.hangfire.io/u/mynameistodd)\
**Post date:** [December 5, 2014, 2:54pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/3 "2014-12-05T14:54:05Z")

</div>

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();
}

```

Thanks!

---

<div class="post-metadata">

**Author:** ![odinserj](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/odinserj/32/648_2.png) [@odinserj](https://discuss.hangfire.io/u/odinserj)\
**Post date:** [December 5, 2014, 3:01pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/4 "2014-12-05T15:01:15Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![odinserj](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/odinserj/32/648_2.png) [@odinserj](https://discuss.hangfire.io/u/odinserj)\
**Post date:** [December 5, 2014, 3:01pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/5 "2014-12-05T15:01:36Z")

</div>

Hope will write more about extensibility in the near future.

---

<div class="post-metadata">

**Author:** ![mynameistodd](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/mynameistodd/32/60_2.png) [@mynameistodd](https://discuss.hangfire.io/u/mynameistodd)\
**Post date:** [December 5, 2014, 3:06pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/6 "2014-12-05T15:06:46Z")

</div>

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

Thanks for the quick reply 😄

---

<div class="post-metadata">

**Author:** ![Denis\_Gomes](https://avatars.discourse-cdn.com/v4/letter/d/7c8e57/32.png) [@Denis\_Gomes](https://discuss.hangfire.io/u/Denis_Gomes)\
**Post date:** [July 14, 2015, 11:33am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/7 "2015-07-14T11:33:05Z")

</div>

> [@odinserj](#):
>
> public void ProlongExpirationTimeAttribute : JobFilterAttribute, IApplyStateFilter  
> {  
> public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)  
> {  
> filterContext.JobExpirationTimeout = TimeSpan.FromDays(7);  
> }  
> }

Hi,  
How can i apply this attribute using [Asp.net](http://Asp.net) Web Application?

Thank you

---

<div class="post-metadata">

**Author:** ![Denis\_Gomes](https://avatars.discourse-cdn.com/v4/letter/d/7c8e57/32.png) [@Denis\_Gomes](https://discuss.hangfire.io/u/Denis_Gomes)\
**Post date:** [July 21, 2015, 9:54pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/8 "2015-07-21T21:54:48Z")

</div>

Hello,  
The @odinserj helped me.

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

```auto
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.  
Denis

---

<div class="post-metadata">

**Author:** ![Lukas\_Roberts](https://avatars.discourse-cdn.com/v4/letter/l/90db22/32.png) [@Lukas\_Roberts](https://discuss.hangfire.io/u/Lukas_Roberts)\
**Post date:** [January 8, 2016, 5:21pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/10 "2016-01-08T17:21:32Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Joel\_Kravets](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/joel_kravets/32/564_2.png) [@Joel\_Kravets](https://discuss.hangfire.io/u/Joel_Kravets)\
**Post date:** [March 15, 2016, 4:26pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/11 "2016-03-15T16:26:45Z")

</div>

If you look at the [SQLServerStorage](https://github.com/HangfireIO/Hangfire/blob/ad008233257cf2ff574ffc737b6203bc41b97cc5/src/Hangfire.SqlServer/SqlServerStorageOptions.cs) class in GitHub you can see the default settings.

```auto
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.

```auto
GlobalConfiguration.Configuration.UseSqlServerStorage("DbConnectionString",
new Hangfire.SqlServer.SqlServerStorageOptions 
{
	JobExpirationCheckInterval = TimeSpan.FromMinutes(1)
});

```

---

<div class="post-metadata">

**Author:** ![samshiles](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/samshiles/32/427_2.png) [@samshiles](https://discuss.hangfire.io/u/samshiles)\
**Post date:** [February 24, 2017, 2:32pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/12 "2017-02-24T14:32:48Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![dynamiclynk](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/dynamiclynk/32/920_2.png) [@dynamiclynk](https://discuss.hangfire.io/u/dynamiclynk)\
**Post date:** [April 3, 2017, 8:45pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/13 "2017-04-03T20:45:19Z")

</div>

@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!

---

<div class="post-metadata">

**Author:** ![wichiforce](https://avatars.discourse-cdn.com/v4/letter/w/e9a140/32.png) [@wichiforce](https://discuss.hangfire.io/u/wichiforce)\
**Post date:** [February 15, 2018, 5:49am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/14 "2018-02-15T05:49:28Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![pieceofsummer](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/pieceofsummer/32/785_2.png) [@pieceofsummer](https://discuss.hangfire.io/u/pieceofsummer)\
**Post date:** [February 15, 2018, 10:15pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/15 "2018-02-15T22:15:11Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Hans\_Engelen](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/hans_engelen/32/1513_2.png) [@Hans\_Engelen](https://discuss.hangfire.io/u/Hans_Engelen)\
**Post date:** [February 19, 2018, 12:35pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/16 "2018-02-19T12:35:42Z")

</div>

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 🙂 …) that will do server side expiration of documents (records for you SQL lubbers 😉 …)

See [Mongo Docs wiki](https://docs.mongodb.com/manual/tutorial/expire-data/)

---

<div class="post-metadata">

**Author:** ![DaveA-W](https://avatars.discourse-cdn.com/v4/letter/d/34f0e0/32.png) [@DaveA-W](https://discuss.hangfire.io/u/DaveA-W)\
**Post date:** [September 12, 2018, 6:11am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/17 "2018-09-12T06:11:32Z")

</div>

(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 [http://docs.hangfire.io/en/latest/tutorials/send-email.html?highlight=jobfilterattribute#logging](http://docs.hangfire.io/en/latest/tutorials/send-email.html?highlight=jobfilterattribute#logging) I’ve added the attribute against the method being called, rather than the caller.

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

```

Which is called by the login controller:

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

```

**Solution ----------------------------**  
Per [https://stackoverflow.com/a/50224619/154170](https://stackoverflow.com/a/50224619/154170), as I am using DI, putting the attribute on the interface method works instead:

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

```

---

<div class="post-metadata">

**Author:** ![Akhil\_Vadiyala](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/akhil_vadiyala/32/1611_2.png) [@Akhil\_Vadiyala](https://discuss.hangfire.io/u/Akhil_Vadiyala)\
**Post date:** [May 29, 2019, 12:15pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/18 "2019-05-29T12:15:34Z")

</div>

It’s not working for me. Are you sure this works?

---

<div class="post-metadata">

**Author:** ![Kaffein\_persona](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/kaffein_persona/32/1973_2.png) [@Kaffein\_persona](https://discuss.hangfire.io/u/Kaffein_persona)\
**Post date:** [February 20, 2020, 6:57am UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/19 "2020-02-20T06:57:48Z")

</div>

I don’t understand the part “Apply Filter Globally”. Where should I apply it? I’m using Web Application with Angular and when I apply it into startup.cs, I can’t get my job to be done. I can still generate new job but they won’t run when it’s time.

---

<div class="post-metadata">

**Author:** ![LucasPereira](https://dub1.discourse-cdn.com/flex017/user_avatar/discuss.hangfire.io/lucaspereira/32/139_2.png) [@LucasPereira](https://discuss.hangfire.io/u/LucasPereira)\
**Post date:** [June 27, 2020, 3:37pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/20 "2020-06-27T15:37:00Z")

</div>

If you want it to applied globally, yes. You will add it to the GlobalJobFilters on the startup. Ex:

```auto
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3, LogEvents = false });

```

---

<div class="post-metadata">

**Author:** ![tcrc7](https://avatars.discourse-cdn.com/v4/letter/t/6de8d8/32.png) [@tcrc7](https://discuss.hangfire.io/u/tcrc7)\
**Post date:** [December 20, 2023, 9:00pm UTC](https://discuss.hangfire.io/t/how-to-configure-the-retention-time-of-job/34/21 "2023-12-20T21:00:53Z")

</div>

I recently started playing around with Hangfire and am trying to use the new way of changing the Job Expiration Timeout by using WithJobExpirationTimeout as mentioned at the bottom of this page ([[Background Methods — Hangfire Documentation](https://docs.hangfire.io/en/latest/background-methods/index.html)]). However, I have been unable to get it to work; it still says it will expire in 1 day. I am using Hangfire 1.8.6 with .Net 8. Below is my code snippet. Am I doing something wrong?

```auto
GlobalConfiguration.Configuration
      .UseSqlServerStorage(builder.Configuration.GetConnectionString("Hangfire"), new SqlServerStorageOptions
      {
          CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
          SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
          QueuePollInterval = TimeSpan.Zero,
          UseRecommendedIsolationLevel = true,
          DisableGlobalLocks = true // Migration to Schema 7 is required
      })
      .WithJobExpirationTimeout(TimeSpan.FromDays(7));

```

I can get it to work if I use the old way by setting up global job filter attribute as mentioned in posts above.

Thanks for the help.
