---
title: Version 25.12 (LTS)
---

# Version 25.12 (LTS)

.. toc::


## Introduction

Version 25.12 is the **Long Term Support (LTS)** release for the version 25 [release cycle](../../organization/policies.md#release-schedule). As an LTS release, it will receive security updates and critical bug fixes for an extended period. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).

## What to know

More details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:

### Python 3.9 removed, Python 3.14 added

Sanic now requires **Python 3.10 or newer**. Python 3.9 has been dropped, and Python 3.14 support has been added.

### Daemon mode

You can now run Sanic as a background daemon process directly from the CLI.

```sh
sanic path.to.app -D
sanic path.to.app --daemon
```

This also introduces convenience commands for managing daemon processes:

```sh
sanic path.to.app status    # Check if running
sanic path.to.app stop      # Stop the daemon
```

Additional options are available:

```sh
--pidfile PATH    # Custom PID file location
--logfile PATH    # Log output to file
--user USER       # Run as specified user
--group GROUP     # Run as specified group
```

Lower-level commands are also available:

```sh
sanic kill --pid=<PID>
sanic kill --pidfile=<PATH>
sanic status --pid=<PID>
sanic status --pidfile=<PATH>
```

### Symlink control for static files

Sanic now provides granular control over symlinks in static file serving with two new parameters:

| Parameter                     | Default | Description                                                                    |
|-------------------------------|---------|--------------------------------------------------------------------------------|
| `follow_external_symlink_files` | `False`   | Allow serving file symlinks that point outside the static root                 |
| `follow_external_symlink_dirs`  | `False`   | Allow serving files from directory symlinks that point outside the static root |

**Examples**

Secure defaults (block external symlinks):
```python
# Symlinks pointing outside /var/www/static will return 404
app.static("/static", "/var/www/static")
```

Allow file symlinks only:
```python
# Serves /var/www/static/config.json -> /etc/app/config.json
app.static("/static", "/var/www/static", follow_external_symlink_files=True)
```

Allow directory symlinks only:
```python
# Serves files from /var/www/static/images/ -> /shared/images/
app.static("/static", "/var/www/static", follow_external_symlink_dirs=True)
```

### Custom configuration converters

You can now extend how Sanic parses environment variables into configuration values. By default, Sanic converts environment variables using `str`, `str_to_bool`, `float`, and `int` converters (tried in reverse order). You can add your own converters to handle custom types.

**Simple converter** - a callable that takes a string and returns a converted value:

```python
class UltimateAnswer:
    def __init__(self, answer):
        self.answer = int(answer)

config = Config(converters=[UltimateAnswer])
app = Sanic("MyApp", config=config)

# Or register after creation
app.config.register_type(UltimateAnswer)
```

**DetailedConverter** - for converters that need more context (full key name, config key, value, and defaults):

```python
from sanic.config import Config, DetailedConverter

class TypeAwareConverter(DetailedConverter):
    def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):
        if config_key in defaults:
            # Cast to the same type as the default value
            return type(defaults[config_key])(value)
        raise ValueError  # Fall through to next converter

config = Config(
    defaults={"PORT": 8000, "DEBUG": False},
    converters=[TypeAwareConverter()]
)
```

### Automatic charset for text content types

Text content types (`text/*`) now automatically include `charset=UTF-8` when serving static files and file responses.

### Task creation returns the task

When creating a task via `app.add_task()`, the asyncio `Task` object is now returned, allowing you to await or check its result later.

```python
task = app.add_task(some_coroutine())
# Later...
result = await task
```

### Improved CLI error messages

Tracerite has been upgraded to v2.2.0, providing better formatted exception tracebacks in the CLI with improved chained exception display.

## Thank you

Thank you to everyone that participated in this release: :clap:

[@ahopkins](https://github.com/ahopkins)
[@amarquard089](https://github.com/amarquard089)
[@ChihweiLHBird](https://github.com/ChihweiLHBird)
[@dhensen](https://github.com/dhensen)
[@dungarpan](https://github.com/dungarpan)
[@gazpachoking](https://github.com/gazpachoking)
[@helioascorreia](https://github.com/helioascorreia)
[@jameslovespancakes](https://github.com/jameslovespancakes)
[@Peopl3s](https://github.com/Peopl3s)
[@tdaron](https://github.com/tdaron)
[@tiejunhu](https://github.com/tiejunhu)
[@tkosman](https://github.com/tkosman)
[@Tronic](https://github.com/Tronic)
[@wojonet](https://github.com/wojonet)


---

If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).
