--- title: "Cómo limitar el ancho de banda con tc en Ubuntu 24.04" description: "Limita el ancho de banda de subida y bajada en Ubuntu 24.04 con tc, TBF, HTB e IFB, prioriza SSH, mídelo con iperf3 y haz la configuración persistente." category: "Configuración de Red" language: es source: https://cubepath.com/es/docs/configuración-de-red/limitacion-de-ancho-de-banda-con-tc-traffic-control updated: 2026-09-25 --- # Cómo limitar el ancho de banda con tc en Ubuntu 24.04 `tc` (traffic control) es la herramienta del paquete `iproute2` que configura cómo el kernel Linux encola y envía los paquetes de cada interfaz. Con ella puedes limitar la velocidad de subida de un servidor, reservar ancho de banda para un servicio, dar prioridad a SSH frente a descargas masivas o limitar también el tráfico de entrada. En este tutorial medirás el ancho de banda actual, aplicarás un límite sencillo con TBF, crearás un reparto por clases con HTB, limitarás la bajada con IFB y harás todo persistente con systemd en Ubuntu 24.04. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. `tc` funciona igual en Debian 12 y Rocky Linux 9. - Un usuario no root con privilegios `sudo`. - Acceso a la consola del servidor: un error con `tc` en la interfaz principal puede cortar tu sesión SSH. - Una segunda máquina con `iperf3` para medir (otro servidor o tu equipo), con el puerto TCP `5201` accesible. Conceptos que usarás: | Término | Qué es | |---------|--------| | qdisc | Disciplina de cola: el algoritmo que decide cómo y cuándo salen los paquetes de una interfaz. | | class | Subdivisión de una qdisc con clases (como HTB), cada una con su propio ancho de banda. | | filter | Regla que decide a qué clase va cada paquete. | | handle | Identificador `mayor:menor`, por ejemplo `1:` para la qdisc raíz y `1:10` para una clase. | Un punto clave: `tc` solo puede **dar forma** al tráfico que sale de una interfaz (egress). El tráfico de entrada ya ha llegado cuando el kernel lo ve, así que para limitar la bajada hay que redirigirlo a una interfaz virtual (paso 5). ## Paso 1: Identificar la interfaz y medir la línea base Averigua qué interfaz usa el servidor para salir a Internet: ```bash ip route get 1.1.1.1 ``` ```output 1.1.1.1 via 203.0.113.1 dev eth0 src 203.0.113.10 uid 1000 ``` La interfaz es la que aparece tras `dev`, aquí `eth0`. En tu servidor puede llamarse `ens18`, `enp1s0` u otro nombre; sustitúyelo en todos los comandos. Consulta la qdisc actual: ```bash tc qdisc show dev eth0 ``` ```output qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64 ``` `fq_codel` es la qdisc por defecto en Ubuntu. Es la que volverá a aplicarse cuando borres tu configuración. Instala `iperf3` en el servidor y en la máquina de pruebas: ```bash sudo apt update sudo apt install iperf3 ``` Si el instalador pregunta si quieres arrancar `iperf3` como servicio, responde **No**. En la máquina de pruebas, deja `iperf3` escuchando: ```bash iperf3 -s ``` Desde el servidor, mide la subida (el servidor envía) durante 10 segundos: ```bash iperf3 -c test_host_ip -t 10 ``` ```output [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 1.09 GBytes 938 Mbits/sec 12 sender [ 5] 0.00-10.00 sec 1.09 GBytes 935 Mbits/sec receiver ``` Y la bajada (el servidor recibe) con `-R`: ```bash iperf3 -c test_host_ip -t 10 -R ``` Apunta ambos valores para comparar después. ## Paso 2: Limitar la subida con TBF TBF (Token Bucket Filter) es la forma más sencilla de poner un techo a toda la interfaz. Aplica un límite de 100 Mbit/s a la salida: ```bash sudo tc qdisc replace dev eth0 root tbf rate 100mbit burst 128kb latency 50ms ``` - `rate 100mbit`: velocidad máxima sostenida. En `tc`, `mbit` son megabits por segundo; `mbps` serían megabytes, un error común. - `burst 128kb`: cuántos bytes pueden salir de golpe. Si es demasiado pequeño, no se alcanza el `rate` configurado. - `latency 50ms`: cuánto puede esperar un paquete en la cola antes de descartarse. - `replace` sustituye la qdisc raíz actual, o la crea si no existe. Comprueba que está activa: ```bash tc -s qdisc show dev eth0 ``` ```output qdisc tbf 8001: root refcnt 2 rate 100Mbit burst 128Kb lat 50ms Sent 185216 bytes 1207 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 ``` Repite la medición de subida: ```bash iperf3 -c test_host_ip -t 10 ``` ```output [ 5] 0.00-10.00 sec 114 MBytes 95.6 Mbits/sec 35 sender ``` El resultado queda algo por debajo de 100 Mbit/s porque `tc` cuenta también las cabeceras. Para quitar el límite y volver a la qdisc por defecto: ```bash sudo tc qdisc del dev eth0 root ``` ## Paso 3: Repartir el ancho de banda con HTB TBF limita todo por igual. Si quieres que SSH siempre responda aunque haya una copia masiva en curso, o reservar una parte para el servidor web, necesitas HTB (Hierarchical Token Bucket), que divide el ancho de banda en clases con un mínimo garantizado (`rate`) y un máximo que pueden tomar prestado (`ceil`). El reparto de ejemplo sobre 100 Mbit/s: | Clase | Tráfico | Garantizado | Máximo | Prioridad | |-------|---------|-------------|--------|-----------| | `1:10` | SSH | 5 Mbit/s | 100 Mbit/s | 0 (más alta) | | `1:20` | Web (80, 443) | 60 Mbit/s | 100 Mbit/s | 1 | | `1:30` | Resto | 35 Mbit/s | 100 Mbit/s | 2 | Crea la qdisc raíz HTB, indicando que el tráfico no clasificado vaya a la clase `1:30`: ```bash sudo tc qdisc replace dev eth0 root handle 1: htb default 30 ``` Crea una clase padre con el total y las tres clases hijas: ```bash sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbit sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit ceil 100mbit prio 0 sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 60mbit ceil 100mbit prio 1 sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 35mbit ceil 100mbit prio 2 ``` Añade una qdisc `fq_codel` dentro de cada clase. Así cada clase reparte su parte de forma justa entre conexiones y mantiene baja la latencia: ```bash sudo tc qdisc add dev eth0 parent 1:10 fq_codel sudo tc qdisc add dev eth0 parent 1:20 fq_codel sudo tc qdisc add dev eth0 parent 1:30 fq_codel ``` Por último, los filtros que envían cada tipo de tráfico a su clase. Como se trata de tráfico de salida, se filtra por **puerto de origen**: son las respuestas del servidor SSH y del servidor web: ```bash sudo tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 22 0xffff flowid 1:10 sudo tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 80 0xffff flowid 1:20 sudo tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 443 0xffff flowid 1:20 ``` Estos filtros `u32` solo clasifican IPv4. El tráfico IPv6 irá a la clase por defecto `1:30`, lo cual funciona pero sin prioridad. Comprueba las clases y cuánto tráfico pasa por cada una: ```bash tc -s class show dev eth0 ``` ```output class htb 1:10 parent 1:1 leaf 8002: prio 0 rate 5Mbit ceil 100Mbit burst 1600b cburst 1600b Sent 48213 bytes 402 pkt (dropped 0, overlimits 0 requeues 0) class htb 1:20 parent 1:1 leaf 8003: prio 1 rate 60Mbit ceil 100Mbit burst 1590b cburst 1600b Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) class htb 1:30 parent 1:1 leaf 8004: prio 2 rate 35Mbit ceil 100Mbit burst 1593b cburst 1600b Sent 912845 bytes 6120 pkt (dropped 0, overlimits 0 requeues 0) ``` Los contadores `Sent` de `1:10` suben mientras usas SSH, lo que confirma que el filtro funciona. Una prueba con `iperf3` (puerto 5201) caerá en `1:30` y podrá usar hasta 100 Mbit/s mientras las otras clases estén libres. ## Paso 4: Limitar el tráfico de una IP de destino Otra necesidad habitual es limitar solo lo que el servidor envía a un destino concreto, por ejemplo una réplica de copias de seguridad en `198.51.100.50`, para que no sature el enlace. Sobre la configuración HTB anterior, añade una clase limitada a 20 Mbit/s sin posibilidad de tomar prestado: ```bash sudo tc class add dev eth0 parent 1:1 classid 1:40 htb rate 20mbit ceil 20mbit prio 3 sudo tc qdisc add dev eth0 parent 1:40 fq_codel sudo tc filter add dev eth0 parent 1: protocol ip prio 3 u32 match ip dst 198.51.100.50/32 flowid 1:40 ``` Verifica que el tráfico hacia esa IP pasa por `1:40`: ```bash tc -s class show dev eth0 classid 1:40 ``` ## Paso 5: Limitar la bajada con IFB Para limitar el tráfico de entrada se redirige a una interfaz virtual IFB (Intermediate Functional Block). Allí el tráfico se trata como salida y se le puede aplicar cualquier qdisc. Carga el módulo y levanta la interfaz `ifb0`: ```bash sudo modprobe ifb numifbs=1 sudo ip link set dev ifb0 up ``` Añade una qdisc de entrada a `eth0` y redirige todo lo que llega a `ifb0`: ```bash sudo tc qdisc add dev eth0 handle ffff: ingress sudo tc filter add dev eth0 parent ffff: matchall action mirred egress redirect dev ifb0 ``` Limita `ifb0` a 50 Mbit/s: ```bash sudo tc qdisc replace dev ifb0 root tbf rate 50mbit burst 128kb latency 50ms ``` Mide la bajada desde el servidor: ```bash iperf3 -c test_host_ip -t 10 -R ``` ```output [ 5] 0.00-10.00 sec 56.9 MBytes 47.7 Mbits/sec receiver ``` Para deshacer la limitación de bajada: ```bash sudo tc qdisc del dev eth0 ingress sudo tc qdisc del dev ifb0 root ``` > Nota: limitar la entrada en tu servidor no reduce lo que llega por el enlace, solo descarta paquetes para que TCP reduzca su velocidad. Sirve para controlar descargas, no como protección contra ataques DDoS. ## Paso 6 (opcional): Simular una red lenta con netem `netem` añade retardo, variación y pérdida de paquetes artificiales. Es útil para probar cómo se comporta una aplicación con una conexión mala. Aplícalo a `ifb0` o a una interfaz de pruebas mejor que a la principal, porque afectará también a tu sesión SSH. Por ejemplo, 100 ms de retardo con 20 ms de variación y un 1 % de pérdida: ```bash sudo tc qdisc replace dev ifb0 root netem delay 100ms 20ms loss 1% ``` Con la redirección del paso 5 activa, `ping -c 5 1.1.1.1` mostrará unos 100 ms más de latencia. Quítalo con: ```bash sudo tc qdisc del dev ifb0 root ``` ## Paso 7: Hacer la configuración persistente Las reglas de `tc` se pierden al reiniciar. Guarda la configuración en un script y ejecútalo con un servicio de systemd. Crea el script: ```bash sudo nano /usr/local/sbin/tc-shaping ``` ```bash #!/usr/bin/env bash set -euo pipefail DEV="eth0" TOTAL="100mbit" DOWN="50mbit" stop() { tc qdisc del dev "$DEV" root 2>/dev/null || true tc qdisc del dev "$DEV" ingress 2>/dev/null || true if ip link show ifb0 >/dev/null 2>&1; then tc qdisc del dev ifb0 root 2>/dev/null || true fi } start() { stop # Subida: HTB con prioridad para SSH y web tc qdisc add dev "$DEV" root handle 1: htb default 30 tc class add dev "$DEV" parent 1: classid 1:1 htb rate "$TOTAL" ceil "$TOTAL" tc class add dev "$DEV" parent 1:1 classid 1:10 htb rate 5mbit ceil "$TOTAL" prio 0 tc class add dev "$DEV" parent 1:1 classid 1:20 htb rate 60mbit ceil "$TOTAL" prio 1 tc class add dev "$DEV" parent 1:1 classid 1:30 htb rate 35mbit ceil "$TOTAL" prio 2 for class in 10 20 30; do tc qdisc add dev "$DEV" parent "1:$class" fq_codel done tc filter add dev "$DEV" parent 1: protocol ip prio 1 u32 match ip sport 22 0xffff flowid 1:10 tc filter add dev "$DEV" parent 1: protocol ip prio 2 u32 match ip sport 80 0xffff flowid 1:20 tc filter add dev "$DEV" parent 1: protocol ip prio 2 u32 match ip sport 443 0xffff flowid 1:20 # Bajada: redirección a ifb0 con TBF modprobe ifb numifbs=1 ip link set dev ifb0 up tc qdisc add dev "$DEV" handle ffff: ingress tc filter add dev "$DEV" parent ffff: matchall action mirred egress redirect dev ifb0 tc qdisc add dev ifb0 root tbf rate "$DOWN" burst 128kb latency 50ms } case "${1:-}" in start) start ;; stop) stop ;; *) echo "Uso: $0 {start|stop}" >&2; exit 1 ;; esac ``` Ajusta `DEV`, `TOTAL` y `DOWN` a tu caso y hazlo ejecutable: ```bash sudo chmod 755 /usr/local/sbin/tc-shaping ``` Crea la unidad de systemd: ```bash sudo nano /etc/systemd/system/tc-shaping.service ``` ```ini [Unit] Description=Limitación de ancho de banda con tc After=network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/sbin/tc-shaping start ExecStop=/usr/local/sbin/tc-shaping stop [Install] WantedBy=multi-user.target ``` Activa el servicio y comprueba que aplica las reglas: ```bash sudo systemctl daemon-reload sudo systemctl enable --now tc-shaping.service systemctl status tc-shaping.service --no-pager ``` ```output ● tc-shaping.service - Limitación de ancho de banda con tc Loaded: loaded (/etc/systemd/system/tc-shaping.service; enabled; preset: enabled) Active: active (exited) since Thu 2026-09-24 12:20:41 UTC; 3s ago ``` ```bash tc qdisc show dev eth0 ``` ```output qdisc htb 1: root refcnt 2 r2q 10 default 0x30 direct_packets_stat 0 direct_qlen 1000 qdisc fq_codel 8005: parent 1:10 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64 ... qdisc ingress ffff: parent ffff:fff1 ---------------- ``` Para desactivar la limitación temporalmente usa `sudo systemctl stop tc-shaping`, y para que no se aplique en el arranque, `sudo systemctl disable tc-shaping`. ## Solución de problemas **`Error: Exclusivity flag on, cannot modify.`**: ya existe una qdisc en esa posición. Usa `tc qdisc replace` en lugar de `add`, o bórrala antes con `tc qdisc del dev eth0 root`. **`RTNETLINK answers: File exists`** al añadir una clase o filtro: ese `classid` ya existe. Ejecuta `sudo /usr/local/sbin/tc-shaping stop` para empezar de cero. **`modprobe: FATAL: Module ifb not found`**: el kernel instalado no incluye el módulo en el paquete base. Instálalo con `sudo apt install linux-modules-extra-$(uname -r)` y repite el paso 5. **No se alcanza la velocidad configurada**: el `burst` es demasiado pequeño para ese `rate`. Súbelo (por ejemplo a `256kb` para varios cientos de Mbit/s) y vuelve a medir. **Todo el tráfico cae en la clase por defecto**: revisa los filtros con `tc filter show dev eth0` y recuerda que en salida se filtra por puerto de **origen** para servicios que el servidor ofrece, y por puerto de **destino** para conexiones que el servidor inicia. ## Conclusión Has medido el ancho de banda con `iperf3`, has aplicado un límite global con TBF, has repartido la subida por clases con HTB dando prioridad a SSH, has limitado la bajada mediante IFB y has hecho la configuración persistente con un servicio de systemd. `tc -s class show` te permite comprobar en cualquier momento cuánto tráfico pasa por cada clase. Como siguientes pasos puedes: - Marcar paquetes con nftables (`meta mark set`) y clasificarlos en `tc` con filtros `fw`, lo que permite reglas más ricas y también para IPv6. - Probar la qdisc `cake`, que combina limitación y reparto justo en una sola línea. - Registrar la evolución del tráfico por clase con tu sistema de monitorización para ajustar los valores `rate` y `ceil`.